Carpe Diem

備忘録

AngularのInjectorとProviderとDependencyの関係

概要

過去の記事で

christina04.hatenablog.com

を書いた時に、AngularのInjectorProviderDependencyの関係を理解していないとよく分からないと思ったので追記的にまとめます。

環境

  • Angular 4.3.5

覚え方

結論から言うと、以下のように考えるとすっきりします。

項目 役割
Injector 料理人
Provider レシピ
Dependency 料理

説明

通常のDIの流れ

f:id:quoll00:20170822094322p:plain

ref: Dependency Injection in Angular by thoughtram

InjectorがProviderを元にDependencyを生成するAngularのDIの流れです。
これを先程の考え方にすると料理人がレシピを見て料理を作るという感じになります。

コードで表すと以下のような関係です。

import { ReflectiveInjector } from '@angular/core';

// injector
var injector = ReflectiveInjector.resolveAndCreate([
  // providers
  { provide: Car, useClass: Car },
  { provide: Engine, useClass: Engine },
  { provide: Tires, useClass: Tires },
  { provide: Doors, useClass: Doors }
]);
          
var car = injector.get(Car);  // dependency

通常のAngularのDIではシンタックスシュガーによってよりシンプルな書き方で扱えるようになってます。

階層的DI

f:id:quoll00:20170822094346p:plain

ref: Dependency Injection in Angular by thoughtram

Angularは親のInjector(料理人A)から子のInjector(料理人B)を生成することができ、そのInjectorは親で用意したProvider(レシピ)からDependency(料理)を生成できます。上の画像では子がCarを生成していますね。
親から子がレシピを教えてもらう、みたいな感じです。

当然Injector(料理人)が違えば作るDependency(料理)も違うものになるので、コード上でも以下のようになります。

var injector = ReflectiveInjector.resolveAndCreate([Engine]);
var childInjector = injector.resolveAndCreateChild([Engine]);

injector.get(Engine) !== childInjector.get(Engine);

Modalの件だとどこに当てはまるか

Angularで中身を動的に変えられるModalを作る【応用編】 - Carpe Diem

では

  open(data: any, provider: Provider): void {
    if (!data) {
      return;
    }

    const providers = ReflectiveInjector.resolve([provider]);
    const injector = ReflectiveInjector.fromResolvedProviders(providers, this.vcr.parentInjector);

    const factory = this.resolver.resolveComponentFactory(data);
    const component = this.vcr.createComponent(factory, this.vcr.length, injector);

としていますが、このinjector

    const injector = ReflectiveInjector.resolveAndCreate(providers);

とはできません。独自のinjector使おうとすると

No provider for ViewUtils!

が出ます。これはCreateComponentする時にComponentのdependency以外に createComponentは内部でComponentFactorycreateというメソッドを使用しているのですが、

  create(
      injector: Injector, projectableNodes: any[][] = null,
      rootSelectorOrNode: string|any = null): ComponentRef<C> {
    const vu: ViewUtils = injector.get(ViewUtils);
    if (!projectableNodes) {
      projectableNodes = [];
    }
    const hostView: AppView<any> = new this._viewClass(vu, null, null, null);
    return hostView.createHostView(rootSelectorOrNode, injector, projectableNodes);
  }

ref: angular/component_factory.ts at ab26b6518d16e54d198031c9994399cc29e270f1 · angular/angular · GitHub

injector.get(ViewUtils);とあるようにViewUtilsProvider(レシピ)が必要です。
これを作ってくれるInjector(料理人)が必要だけど、独自だとそのProvider(レシピ)を持ってないから作れないのです。 なのでvcrparentInjectorから引っ張ってくる必要があります。

まとめ

基本的にAngularはInjectorを意識しなくても扱えるようになっていますが、複雑なことをするときに必要になる知識だと思うのでその時に関係性を把握していると理解の助けになると思います。

ソース