Angularのinject、メソッド内で呼んだらNG203で怒られた話(なんで?)
Ionic+Angularでスマホアプリを作っていて、
システム全体の都合上、過去バージョンの資源(Routerで管理)もアプリ内で持っていないといけないと言う制約のため、
バージョンごとにクラスを継承に次ぐ継承を行うような形で作っていたのですが、
新しい書き方のstandaloneで作り直したら綺麗にできるのでは?という安直な考えと、
毎回overrideのconstrucor書くのがマジで面倒くさくなってきたし、
最近よく見るinject使って必要なものだけ引き継げばいいじゃんみたいなノリで書いていた時にハマったお話。
この作りが正解なのかはわからないけど、前バージョンのコードをまるっとフォルダごとコピーして変更点のある機能だけ直すみたいな、無駄なソースを量産するような作りにウンザリしていたので、class継承で必要な関数だけ直す方が良くね?ということでこんな作りにしている。
起きたこと
Angularで「constructor書きたくないな〜」と思って、最近よく見るinject()を使おうとした。
someMethod() {
const service = inject(MyService);
service.doSomething();
}
→ 実行
NG0203: inject() must be called from an injection context
お前、どこでも呼べるんとちゃうんかい???
結論
inject()はどこでも呼べるわけじゃない。
DIコンテキストの中でしか使えない。
(#^ω^)ピキピキ
そんなわけで、まぁ色々と調べたよ。。。
NGな例(今回のやつ)
someMethod() {
const service = inject(MyService); // ❌ NG
}
これはただのメソッドなので、Angular的には
👉「DIの文脈じゃないよ?」って判断される
OKな使い方
① コンポーネントのフィールド初期化
private service = inject(MyService);
これはOK。なぜなら
👉 コンポーネント生成時(DIコンテキスト内)だから
② constructor(従来通り)
constructor(private service: MyService) {}
これはもちろんOK
③ factory関数(重要)
const myFactory = () => {
const service = inject(MyService);
return new Something(service);
};
これもOK
👉 AngularがDIとして呼び出すから
なぜダメなのか
AngularのDIは
「Angularがインスタンスを作るタイミング」でしか効かない
つまり:
- コンポーネント生成時 → OK
- provider解決時 → OK
- 普通のメソッド実行 → ❌ Angular関係ない
よくある勘違い
❌ injectは便利なグローバル関数
ではない
⭕ AngularのDIライフサイクルに依存した関数
じゃあどうすりゃいいのよ?
パターン1:普通にフィールドで持つ
private service = inject(MyService);
someMethod() {
this.service.doSomething();
}
パターン2:どうしても遅延したい場合
private _service?: MyService;
get service() {
return this._service ??= inject(MyService);
}
※ただしこれも「呼ばれるタイミングがDIコンテキスト内であること」が前提
そもそも、なぜinjectがあるの?
- constructorを汚したくない
- standalone APIと相性がいい
- 関数ベースDIを可能にする
要はコレを使うと 「Angularの設計を関数寄りにするための仕組み」 に出来るよって話。
まとめ
- injectはどこでも使えるわけじゃない
- AngularのDIコンテキスト内限定
- メソッド内で呼ぶと普通に怒られる
安易に「constructor書くのダルいからinject使おう」→「どこでも呼べるやろ」みたいな発想はイケてないね。