4
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 5 years have passed since last update.

Angularの依存性解決の流れ

Last updated at Posted at 2020-01-27

AngularはDI(Dependency Injection)の機構を備えており、constructorなどを通じてサービスなどをコンポーネントへ注入することができます。
サービスの@InjectableデコレータでprovidedIn: 'root'と書いておけば、もう何も考えることなくconstructorでサービスクラスを指定して利用できる優れモノですが、はたしてAngularはどのようにして注入するものを探してくるのでしょう。

注入物は一体どこから来るのか

動作を確認するためにこんな感じのサンプルを用意します。

AppModule ─────┐
│ AppComponent │
└─┬────────────┘
  └ ParentModule ─────┐
    │ ParentComponent │
    └─┬───────────────┘
      └ ChildModule ─────┐
        │ ChildComponent │
        └────────────────┘

各モジュール、各コンポーネントにはそれぞれプロバイダを持たせ、注入物の出自がわかるようにします。

app.module.ts
@NgModule({
  declarations: [AppComponent],
  imports: [BrowserModule, ParentModule],
  providers: [{ provide: AnyService, useValue: { injectLevel: 'root' } }],
  bootstrap: [AppComponent]
})
export class AppModule {}
app.component.ts
@Component({
  selector: 'app-root',
  templateUrl: './app.component.html',
  styleUrls: ['./app.component.scss'],
  providers: [
    { provide: AnyService, useValue: { injectLevel: 'root component' } }
  ]
})
export class AppComponent {
  constructor(public anyService: AnyService) {}
}

各コンポーネントではconstructorでanyServiceを注入し、injectLevel(サービスが提供される階層)をViewに表示させます。

app.component.html
<div>root: {{ anyService.injectLevel }}</div>
<app-parent></app-parent>
parent.component.html
<div>parent: {{ anyService.injectLevel }}</div>
<app-child></app-child>

結果は以下のようになります。
image.png

当たり前ですね。自身のコンポーネントクラスで欲しいものが提供されているのだから、もちろんそのまま注入されています。
では、ChildComponent -> ParentComponent -> AppComponent と順番にコンポーネントクラスのプロバイダを削除していくとどうなるでしょう。

image.png

image.png

image.png

自身のコンポーネントクラス内に注入対象が提供されていない場合、Angularは親クラスへ遡ってプロバイダを探します。
rootコンポーネント(ここではAppComponent)まで遡り、なお見つからない場合はrootモジュール(ここではAppModule)を探しに行きます。

ではrootモジュールにもプロバイダがない場合、Angularはエラーを返すのでしょうか?
さらにプロバイダを削除していくと、以下のようになります。

image.png

image.png

rootモジュールにプロバイダがなかった場合、Angularはrootモジュールのimportsをたどってさらにプロバイダを探します。
rootモジュールが出発点であるため、ParentModule -> ChildModule の順番となっています。
すべてのモジュールを走査してなおプロバイダが見つからない場合、NullInjectorがエラーを返します。

image.png

まとめ

AngularがDIを要求したクラスから出発して依存性を解決していく流れを追いかけました。

  • DIを要求したクラス自身がプロバイダを持っている場合は、それを優先する
  • 自身がプロバイダを持っていない場合、先祖をたどってプロバイダを探す
  • rootモジュールまで到達したら、importsのモジュールをたどってさらにプロバイダを探す

プロバイダの位置を工夫することによって、DIトークン(ここではAnyServiceクラス)が同じであっても全く違うものを注入させたり、注入可能な範囲を制限したりすることができます。
ですが、特別な理由がなければprovidedIn: 'root'でいいんじゃないかと個人的には思います。

4
0
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
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?