AngularはDI(Dependency Injection)の機構を備えており、constructorなどを通じてサービスなどをコンポーネントへ注入することができます。
サービスの@InjectableデコレータでprovidedIn: 'root'と書いておけば、もう何も考えることなくconstructorでサービスクラスを指定して利用できる優れモノですが、はたしてAngularはどのようにして注入するものを探してくるのでしょう。
注入物は一体どこから来るのか
動作を確認するためにこんな感じのサンプルを用意します。
AppModule ─────┐
│ AppComponent │
└─┬────────────┘
  └ ParentModule ─────┐
    │ ParentComponent │
    └─┬───────────────┘
      └ ChildModule ─────┐
        │ ChildComponent │
        └────────────────┘
各モジュール、各コンポーネントにはそれぞれプロバイダを持たせ、注入物の出自がわかるようにします。
@NgModule({
  declarations: [AppComponent],
  imports: [BrowserModule, ParentModule],
  providers: [{ provide: AnyService, useValue: { injectLevel: 'root' } }],
  bootstrap: [AppComponent]
})
export class AppModule {}
@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に表示させます。
<div>root: {{ anyService.injectLevel }}</div>
<app-parent></app-parent>
<div>parent: {{ anyService.injectLevel }}</div>
<app-child></app-child>
当たり前ですね。自身のコンポーネントクラスで欲しいものが提供されているのだから、もちろんそのまま注入されています。
では、ChildComponent -> ParentComponent -> AppComponent と順番にコンポーネントクラスのプロバイダを削除していくとどうなるでしょう。
自身のコンポーネントクラス内に注入対象が提供されていない場合、Angularは親クラスへ遡ってプロバイダを探します。
rootコンポーネント(ここではAppComponent)まで遡り、なお見つからない場合はrootモジュール(ここではAppModule)を探しに行きます。
ではrootモジュールにもプロバイダがない場合、Angularはエラーを返すのでしょうか?
さらにプロバイダを削除していくと、以下のようになります。
rootモジュールにプロバイダがなかった場合、Angularはrootモジュールのimportsをたどってさらにプロバイダを探します。
rootモジュールが出発点であるため、ParentModule -> ChildModule の順番となっています。
すべてのモジュールを走査してなおプロバイダが見つからない場合、NullInjectorがエラーを返します。
まとめ
AngularがDIを要求したクラスから出発して依存性を解決していく流れを追いかけました。
- DIを要求したクラス自身がプロバイダを持っている場合は、それを優先する
 - 自身がプロバイダを持っていない場合、先祖をたどってプロバイダを探す
 - rootモジュールまで到達したら、importsのモジュールをたどってさらにプロバイダを探す
 
プロバイダの位置を工夫することによって、DIトークン(ここではAnyServiceクラス)が同じであっても全く違うものを注入させたり、注入可能な範囲を制限したりすることができます。
ですが、特別な理由がなければprovidedIn: 'root'でいいんじゃないかと個人的には思います。






