LoginSignup
4
2

More than 1 year has passed since last update.

AngularのInjectableまとめ

Last updated at Posted at 2022-07-31

経緯

こちらのページで学習中、NG0200: Circular dependency in DI detected for EmployeesService. のエラーに遭遇しました。

firestore.service.ts
// @Injectableを書かないと、このservice内でDIしているAngularFirestoreにアクセスした際に
// NG0200: Circular dependency in DI detected for EmployeesService. が出る
@Injectable()
export abstract class FirestoreService<T> {

}

エラーの解決方法としては上記の通り@Injectable()を付けるだけなのですが、せっかくなので内部で何が起きているかを調べてみたいと思います。


ng-japan OnAir vol.30 "Injectableを再発見!"

まずはこちらで学習します。

(参考)
公式サイト:Angular-Injectable
Angular v6で導入されるTree-Shakable DIの紹介
Angular2のDIを知る

Injectableについて

  • Injectableは、そのclassを提供(provide)可能・注入(inject)可能にするデコレータ
  • 逆に言えば、そのclassが注入されるためには、Injectableがついていなければいけない(constructorでDIされるclassには基本的にInjectableが必要)
  • v2から登場したInjectableに、v6でprovidedInのオプションが入った
  • Tree-Shakable
  • v5までは、DIする場合はNgModuleproviderに登録しなければならなかった(NgModuleからclassを参照する)
  • どこからもInjectionされないclassでも、Providerに登録する時点でNgModuleからの参照が発生し、バンドルに入ってしまうという問題があった
  • そこでv6で@Injectableに機能追加を行い、どの単位で参照するかをclass自身が持つようにした(providedInで指定)
  • これにより逆にclassNgModuleを参照するようになり、自身がComponentや他のclassから参照されなければ、どこからも参照されない状態になった

@Injectable()のprovidedInプロパティを使用することは、 @NgModule()のproviders 配列を使用するよりも望ましい方法です。 なぜなら、@Injectable()のprovidedInを使用することで、 最適化ツールはアプリケーションで使用されていないサービスをツリーシェイキングし、 バンドルサイズをより小さくできるからです。
公式サイトより)

  • providedInのoptionとしてはrootplatformanyがあるが、ほとんどrootしか使わない
  • 'root' : The application-level injector in most apps.
  • angularアプリに対して1個のインスタンスを作る(bootstrap単位でのシングルトン)

ngcコマンドの結果を使用したデバッグについて

  • ngc コマンドでコンパイル後のjsを見ることができる
  • @Injectableɵfacɵprovを作る
  • ɵfacはそのclassInjectするためのインスタンスをどうやって作るかを定義するFactory関数
  • ɵfacを通ってインスタンスが作られないとDIされない
  • つまり「InjectされるためにはInjectableがついていなければいけない」というのは、ɵfacを作ってインスタンスの作り方を定義しなければならないということ
  • ɵprovInjectableをどうprovideするかの設定情報
  • hogeClass.ɵfac is not functionのエラーはInjectableの付け忘れ
  • ɵはキーボードで入力できないことから、参照しないでアピールで使う
  • Angularに詳しくなりたい方はngcの結果を読むのはオススメ

コンパイル後のjsを比較してみる

次の3パターンでngcコマンドでコンパイルされたjsを比較します。

パターン @Injectable provideIn
1 あり なし
2 あり root
3 なし -

パターン1 : @Injectableあり、provideIn指定なし

firestore.service.ts
import { AngularFirestore, QueryFn } from "@angular/fire/compat/firestore";

@Injectable()
export abstract class FirestoreService<T> {

  constructor(protected firestore: AngularFirestore;) {}

}
firestore.service.js
import * as i0 from "@angular/core";
import * as i1 from "@angular/fire/compat/firestore";
export class FirestoreService {

}
FirestoreService.ɵfac = function FirestoreService_Factory(t) { return new (t || FirestoreService)(i0.ɵɵinject(i1.AngularFirestore)); };
FirestoreService.ɵprov = /*@__PURE__*/ i0.ɵɵdefineInjectable({ token: FirestoreService, factory: FirestoreService.ɵfac });
(function () { (typeof ngDevMode === "undefined" || ngDevMode) && i0.ɵsetClassMetadata(FirestoreService, [{
        type: Injectable
    }], function () { return [{ type: i1.AngularFirestore }]; }, null); })();

ɵfacɵprovが作られていることが分かります。

パターン2 : @Injectableあり、provideIn:'root'

firestore.service.ts
import { AngularFirestore, QueryFn } from "@angular/fire/compat/firestore";

@Injectable({
  providedIn: 'root'
})
export abstract class FirestoreService<T> {

  constructor(protected firestore: AngularFirestore;) {}

}
firestore.service.js
import * as i0 from "@angular/core";
import * as i1 from "@angular/fire/compat/firestore";

FirestoreService.ɵfac = function FirestoreService_Factory(t) { return new (t || FirestoreService)(i0.ɵɵinject(i1.AngularFirestore)); };
FirestoreService.ɵprov = /*@__PURE__*/ i0.ɵɵdefineInjectable({ token: FirestoreService, factory: FirestoreService.ɵfac, providedIn: 'root' });
(function () { (typeof ngDevMode === "undefined" || ngDevMode) && i0.ɵsetClassMetadata(FirestoreService, [{
        type: Injectable,
        args: [{
                providedIn: 'root'
            }]
    }], function () { return [{ type: i1.AngularFirestore }]; }, null); })();

ɵɵdefineInjectable の引数となっているオブジェクトのプロパティにprovidedIn: 'root'が追加されています。

パターン3 : @Injectableなし

firestore.service.ts
import { AngularFirestore, QueryFn } from "@angular/fire/compat/firestore";

// @Injectable()   <-- なし
export abstract class FirestoreService<T> {

  constructor(protected firestore: AngularFirestore;) {}

}
firestore.service.js
// import * as i0 from "@angular/core";  <-- なくなった
// import * as i1 from "@angular/fire/compat/firestore";  <-- なくなった
export class FirestoreService {

}
// ↓ なくなった
// FirestoreService.ɵfac = function FirestoreService_Factory(t) { return new (t || FirestoreService)(i0.ɵɵinject(i1.AngularFirestore)); };
// FirestoreService.ɵprov = /*@__PURE__*/ i0.ɵɵdefineInjectable({ token: FirestoreService, factory: FirestoreService.ɵfac });
// (function () { (typeof ngDevMode === "undefined" || ngDevMode) && i0.ɵsetClassMetadata(FirestoreService, [{
//         type: Injectable
//     }], function () { return [{ type: i1.AngularFirestore }]; }, null); })();

ɵfacɵprovが作られていません。
また、factory関数内のi0.ɵɵinject(i1.AngularFirestore)が消え、そもそもAngularFirestoreがimportされていません。

firestore.service.ts@Injectable()をつけないとNG0200: Circular dependency in DI detected for EmployeesService. が出る問題は、これが原因と思われます。

が、なんでCircular dependencyになるのかは分かりません・・・


  • /*@__PURE__*/について


@Injectable()ProvidedInの指定なし)は何を意味するのか?

providedIn
Determines which injectors will provide the injectable.

providedIn?: Type | 'root' | 'platform' | 'any' | null

  • 'null' : Equivalent to undefined. The injectable is not provided in any scope automatically and must be added to a providers array of an @NgModule, @Component or @Directive.

公式サイトより)

  • providedInは、どのinjectorinjectableをprovideするか決めるもの
  • @Injectable()@Injectable({providedIn:null})同じで、どのスコープにも自動的にはprovideされない

ちなみに、injectorは3種類あります

Injector configuredBy providedIn option 備考
NullInjector @Optionalを使わない限りエラーを投げる
ModuleInjector PlatformModule platform
root ModuleInjector AppModule root

firestore.service.ts
@Injectable()
export abstract class FirestoreService<T> {

}

@Injectable()をつけただけではprovideされないので、FirestoreServiceをどこかのclassInjectするとエラーが出ると予想されます。

employees.service.ts
import { FirestoreService } from './firestore.service';

export class EmployeesService {
  constructor(private fs: FirestoreService<Employee>) { }
}

実行するとやはりエラーが出ました。
NullInjectorError: No provider for FirestoreService!

他のclassで使いたいclassには@Injectable()を付けるだけでは十分でなく、providedInInjectorを決めてやる必要があるようです。

4
2
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
4
2