環境
- Angular 8
- Node.js 12.13.0
NullInjectorErrorは突然に
実装した画面を開発環境で実際に確認するべく、いつも通りng build --prod
してS3に格納。CloudFrontのCreate Invalidationをやっていざ確認!と思ってページを開いてみたら…。
Uncaught NullInjectorError: StaticInjectorError(AppModule)
以前にもこのエラーが出たことがあったが、その時はServiceクラスをCoreModuleのProvidersに追加してないせいだったのでProvidersに追加して問題は解決した。
今回も新しく作成したServiceクラスをProvidersに追加し忘れたかなと思って確認した。
が、全て追加されていた。
しかも、
[CoreModule -> ParamDecoratorFactory]
なんだこれ…見たことない…。
原因はDecoratorの書き方にあった
同じチームの神ことKさんが気になる記事を発見してくださった。(ありがたやありがたや…)
TypeScript - デコレータ
どうやらParamDecoratorFactoryはデコレーターに関係あるっぽい…?
デコレーターを使っている箇所…?あ…。
今回のプロジェクトでは、ディレクトリ構成を以下のようにしていた。
参考: How to define a highly scalable folder structure for your Angular project
app
├ core
│ ├ services
│ └ core.module.ts
├ modules
│ ├ moduleA
│ ├ moduleB
│ └ moduleC
├ shared
│ ├ components
│ ├ pipes
│ └ interface
└ app.module.ts
基本的にServiceクラスは全てCoreModuleに集め、そこのProvidersに追加、CoreModuleをAppModuleにimportという形を取っていた。
さらにCoreModuleがAppModule以外から呼ばれないよう、AppModule以外から呼ばれたら例外を投げるようにしていた。
以下その設定をするソースコード↓(この設定方法は割とどこでも書いてあるやつ)
export class CoreModule {
// CoreModuleのimportはAppModuleのみ。それ以外でimportをすると例外を投げる。
// @ts-ignore
constructor(@Optional @SkipSelf parentModule: CoreModule) {
if (parentModule) {
throw new Error(`CoreModule is already loaded. Import it in the AppModule only`);
}
}
}
ここでデコレーターを使っているではないか!
コンパイルエラーで教えてくれていたのに、当時の自分はここを @ts-ignore
を使って潰してしまっていたのだ。
TypeScriptでデコレータを使う時は括弧を付ける必要があった。
そんなこんなで以下のように修正(してもらった)
export class CoreModule {
// CoreModuleのimportはAppModuleのみ。それ以外でimportをすると例外を投げる。
constructor(@Optional() @SkipSelf() parentModule: CoreModule) {
if (parentModule) {
throw new Error(`CoreModule is already loaded. Import it in the AppModule only`);
}
}
}
これで再度ProdビルドしてS3にデプロイ、CloudFrontをいじったら正常に表示されるようになった。
まとめ
TypeScriptでDecorator使う時は括弧を忘れずにしよう