この記事は Angular Advent Calendar 2019 三日目の記事です。
こんにちは。カルテットコミュニケーションズ で働いている @ringtail003 です。この記事は、公式ドキュメント Angular の依存性の注入 | angular.jp を読みつつ手元で試して理解した事を「こう書いたら、こう動く」視点でまとめたものです。クッソ長いですが、ナナメ読みして後からかんたんにテキスト検索できるように情報をてんこ盛りにしておきましたので、ぜひ開発の参考にしていただけたらと思います。
この記事の対象読者:
- 公式ドキュメントを読んで「なるほど、よく分からん!」になっている人
- なんとなく DI 使っているけど時々詰まる人
この記事を書いた環境:
- node v12.13
- @angular/cli 8.3.12
記事の中のサンプルコードは GitHub にて公開しています。
テストもパスする状態になっていますので良ければ参考にしてください。
https://github.com/ringtail003/learn-angular-di
最初の DI
サービスの作成
初めに「サービスクラスをコンポーネントに注入する」という例を紹介します。
下記は CLI コマンドで $ ng generate service greet
を実行し、生成されたサービスクラスの雛形にメソッドを追加したものです。
import { Injectable } from '@angular/core';
@Injectable({
providedIn: 'root'
})
export class GreetService {
constructor() { }
greet(name: string) {
return `Hello ${name}!`;
}
}
サービスクラスには @Injectable
デコレータが自動的に付与されています。これは Angular が このサービスクラスが他のサービスクラスやコンポーネントに注入可能である 事をマークアップしたものです。そしてオブジェクトリテラルの providedIn: 'root'
は アプリケーションのどこからでもこのサービスが利用可能な事 を示しています。
コンポーネントに注入
import { Component, OnInit } from '@angular/core';
import { GreetService } from 'src/app/services/greet.service';
@Component({
selector: 'some',
templateUrl: './some.component.html',
styleUrls: ['./some.component.scss']
})
export class SomeComponent implements OnInit {
message: string = null;
constructor(
private greetService: GreetService,
) { }
ngOnInit() {
this.message = this.greetService.greet('Angular DI');
// 'Hello Angular DI!'
}
}
コンポーネントのコンストラクタの引数に GreetService
を指定すると Angular によってサービスクラスのインスタンスが注入されます。それに伴い private
アクセス修飾子を指定しているため、コンポーネントクラスの中から this.greetService
のようにアクセスする事ができます。
サンプルコード/最初のDI
https://github.com/ringtail003/learn-angular-di/tree/01-injectable-service
DI コンテナとインジェクター
@Injectable
でマークアップしたサービスクラスは コンテナ に格納されます。コンテナにはさまざまな値やサービスが格納されるため、それらを一意にするための 識別子(トークン) が付与されます。そして、トークンを判別してコンテナから値やサービスクラスを取り出す役割は インジェクター が担っています。
以降は下記のように表記を統一します。
- コンテナに格納されるサービスクラスや値: 依存オブジェクト
- コンストラクタの引数で依存オブジェクトを要求する事: 注入の要求
- トークンを見つけて依存オブジェクトを取り出せる状態にする事: 依存の解決
- 依存オブジェクトを取り出してコンストラクタに渡す事: 依存の注入
インジェクターツリー
Angular ではコンポーネントと 1 対 1 の関係でコンポーネントの専用インジェクター( エレメントインジェクター )が存在します。そしてコンポーネントのツリー構造と並列して、インジェクターもツリー構造の関係性を持ちます。
さらにコンポーネント群が機能単位でモジュール分割されている場合、それぞれのモジュールにも専用インジェクター( モジュールインジェクター )が存在します。
CLI コマンドで作成したサービスクラスに自動的に付与される @Injectable({ providedIn: 'root' })
をインジェクターツリーに照らし合わせると、一番根っこに位置するルートインジェクターがこのサービスクラスの依存オブジェクトを管理し、依存解決する事を示しています。
コンポーネントから注入が要求された場合、下記のような順番で依存解決するインジェクターの探索が行われます。
- そのコンポーネントのインジェクター
- DOM の親要素のコンポーネントのインジェクター
- DOM の祖先要素のコンポーネントのインジェクター
- コンポーネントが登録されているモジュールのインジェクター
- 祖先モジュールのインジェクター
依存解決するインジェクターが見つかると探索はそこで終了しますが 見つからない場合は最終的にルートモジュールのインジェクター にたどり着きます。
ルートモジュールのインジェクターが依存解決できない場合は、さらに上位の NullInjector という特別なインジェクターに処理が委譲され、その中で例外処理されます。
NullInjectorError: \
StaticInjectorError(AppModule)[SomeComponent -> SomeService]:
このエラーメッセージ、たまに出てきますよね。
これはインジェクターツリーを辿ってルートモジュールのインジェクターまで到達したものの、依存解決するインジェクターが見つからず、注入の要求が失敗した事を示しています。
インジェクターの指定
Angular では @Injectable({ providedIn: 'root' })
のように 依存解決するインジェクターを指定する事でコンテナへの登録 が行われます。
インジェクターの指定をするためのデコレータを下記表にまとめました。
デコレータ | 登録できるもの | インジェクター | ツリーシェイキング |
---|---|---|---|
@Injectable |
クラス | モジュール | される |
@NgModule |
クラス、ファクトリ関数、値 | モジュール | されない |
@Component |
クラス、ファクトリ関数、値 | エレメント | されない |
@Injectable
クラス名がトークンになります。
// 宣言
@Injectable({...})
class GreetService {}
// 注入
@Component({...})
class SomeComponent {
constructor(
greetService: GreetService,
) {}
}
TypeScript のデコレータの仕様上、関数や値を依存オブジェクトに指定する事はできません。
// error TS1206: Decorators are not valid here.
@Injectable({
providedIn: 'root'
})
export function some() {}
ルートモジュールのインジェクターではなく、任意のモジュールインジェクターを指定するにはモジュールクラスを指定します。
@Injectable({
providedIn: SomeModule
})
class GreetService {}
依存を入れ子にする事もできます。
@Injectable({...})
class GreetService {
constructor() {}
}
@Injectable({...})
class OtherService {
constructor(
private greetService: GreetService, // GreetService への依存
) {}
}
また @Injectable
による指定は ツリーシェイキングが有効になるため どこからも利用されていない依存オブジェクトはビルドファイルに含まれず、サイズが小さくなります。
サンプルコード/インジェクターの指定
@Injectable
https://github.com/ringtail003/learn-angular-di/tree/02-injector-injectable
@NgModule
provide
で指定したクラスがトークンになります。
// 宣言
@NgModule({
providers: [
{ provide: GreetService, useClass: GreetService }
],
})
// 注入
@Component({...})
class SomeComponent {
constructor(
greetService: GreetService,
) {}
}
クラス以外にも、ファクトリ関数や値を依存オブジェクトとして指定する事ができます。
@NgModule({
providers: [
{ provide: GreetService, useValue: { greet: () => {} } }
],
})
@Injectable
と @NgModule
はどちらもモジュールインジェクターに対する指定です。両方から同じモジュールに対して同じトークンを指定した場合は @NgModule
が優先されます。
サンプルコード/インジェクターの指定
@NgModule
https://github.com/ringtail003/learn-angular-di/tree/02-injector-ngmodule
@Component
provide
で指定したクラスがトークンになります。
// 宣言
@Component({
providers: [
{ provide: GreetService, useClass: GreetService }
],
})
class SomeComponent {
// 注入
constructor(
greetService: GreetService,
) {}
}
クラス以外にも、ファクトリ関数や値を依存オブジェクトとして指定する事ができます。
@Component({
...
providers: [
{ provide: GreetService, useValue: { greet: () => {} } }
],
})
サンプルコード/インジェクターの指定
@Component
https://github.com/ringtail003/learn-angular-di/tree/02-injector-component
注入のチューニング
@NgModule
または @Component
の providers
はインジェクターに対して、依存オブジェクトの注入方法を指定する事ができます。
シンタックスは下記の通りです。
('providers' | 'viewProviders'): [
{
provide: {クラス名},
useClass?: {クラス名},
useValue?: {値},
useFactory?: {関数},
multi?: boolean,
deps?: any[],
},
]
provide
にはトークンとして使用する クラスまたは抽象クラス を指定します。
インターフェースはビルドを経て TypeScript から JavaScript にトランスパイルすると消えてしまう情報のためトークンとして使用する事ができません。
@NgModule({
providers: [
{ provide: SomeService, useClass: SomeService },
]
})
class SomeModule {}
下記のように省略すると provide
と useClass
に同じクラスを指定した事になります。
@NgModule({
providers: [SomeService],
})
useClass
{
provide: SomeService,
useClass: SomeService,
}
インジェクターはクラスのインスタンス( new SomeService()
)を注入します。
useValue
{
provide: SomeService,
useValue: { method: () => {} }
}
インジェクターは値を注入します。
上記コードの場合は { method: () => {} }
というオブジェクトリテラルを注入します。
useFactory
{
provide: SomeService,
useFactory: () => isAuthorized ? { user: '' } : {},
}
インジェクターはファクトリ関数を実行した戻り値を注入します。
関数でラップする事で動的に値を作成できるため、状況によって変化を付けたい場合などに利用できます。
また deps
を使うとファクトリ関数に依存を注入できます。
{
provide: SomeService,
useFactory: (otherService: OtherService) => {
return { value: otherService.value };
},
deps: [OtherService]
}
useExisting
{
provide: SomeService,
useExisting: SomeService,
}
すでに存在しているトークンをエイリアスとして取り出します。
ちょっとややこしいので useClass
を使った場合と比較してみましょう。
@Component({
providers: [
{ provide: SomeService, useClass: SomeService },
{ provide: OtherService, useClass: SomeService },
],
})
-
SomeService
を要求するとnew SomeService()
が注入されます -
OtherService
を要求するとnew SomeService()
が注入されます
つまり同じサービスクラスであるにも関わらず、インスタンスは2回作成される事になります。
次は useExisting
を使った場合です。
@Component({
providers: [
{ provide: SomeService, useClass: SomeService },
{ provide: OtherService, useExisting: SomeService },
],
})
-
SomeService
を要求するとnew SomeService()
が注入されます -
OtherService
を要求するとSomeService
のインスタンスの参照が注入されます
インスタンスの作成は1回です。このように useExisting
は他のトークンのエイリアスとして動作します。
multi
{
provide: SomeService,
useValue: {},
multi: true,
}
true を指定すると同じトークンで複数の依存オブジェクトを扱う事ができます。
@Component({
providers: [
{ provide: SomeService, useValue: { value: 1 }, multi: true },
{ provide: SomeService, useValue: { value: 2 }, multi: true },
{ provide: SomeService, useValue: { value: 3 }, multi: true },
]
})
export class SomeComponent implements OnInit {
constructor(
someService: SomeService,
) {
console.info(someService); // [{ value: 1 }, { value: 2 }, { value: 3 } ]
}
multi を指定した場合、配列が注入されます。
viewProviders
viewProvider
を使用すると <ng-content>
に展開されるコンポーネントに対してインジェクターの伝播を指定する事ができます。
name
というプロパティを持ったサービスを宣言します。
@Injectable({
providedIn: 'root'
})
export class NameService {
name: string = 'my name is name service';
}
<inner>
コンポーネントは NameService.name
を表示します。
@Component({
template: `<p>{{nameService.name}}</p>`,
})
class InnerComponent {
constructor(
nameService: NameService,
) {}
}
<outer>
コンポーネントは NameService.name
を表示し、親コンポーネントからコンテンツを受け取って表示します。
@Component({
template:
'<p>{{nameService.name}}<p>' +
'<ng-content></ng-content>'
})
class OuterComponent {
constructor(
nameService: NameService,
) {}
}
app.component の html はこのようになっています。
<outer>
<inner></inner>
</outer>
ブラウザのレンダリング結果はこのようになります。
my name is name service.
my name is name service.
次に <outer>
コンポーネントのエレメントインジェクターで NameService
の注入方法を指定します。
これで <outer>
コンポーネントには独自の NameService
が注入されるようになります。
@Component({
template: `<p>{{nameService.name}}<ng-content></ng-content><p>`,
providers: [
{ provide: NameService, useValue: { name: 'my name is outer.' } },
],
})
class OuterComponent {
constructor(
nameService: NameService,
) {}
}
ブラウザのレンダリング結果はこのように変わります。
<outer>
コンポーネントは <inner>
を <ng-content>
によって内包しているため、インジェクターが伝播されています。
my name is outer.
my name is outer.
この挙動が好ましくない場合は providers
を viewProviders
に変更する事で伝播を止める事ができます。
- providers: [
+ viewProviders: [
{ provide: NameService, useValue: { name: 'my name is outer.' } },
],
ブラウザのレンダリング結果はこのように変わります。
my name is name service.
my name is outer.
サンプルコード/インジェクターの指定
viewProviders
https://github.com/ringtail003/learn-angular-di/tree/02-view-provider
ツリー探索のチューニング
インジェクターツリーの探索は パラメータデコレータ を使う事でチューニングする事ができます。
ツリーの相対的な位置関係を示すため、以降の「自身」とは 注入を要求されたインジェクター の事を示しています。
デコレータ | 挙動 |
---|---|
@Optional |
ルートインジェクターまで辿ってトークンが見つからない場合 null を返す |
@Self |
ツリー探索せず自身の所有するトークンのみ探す |
@SkipSelf |
自身をスキップしてツリー探索をスタート |
@Host |
ツリー探索の終端である事を示す |
@Optional
トークンが見つからない場合に例外処理せず null
を注入します。
// どこのインジェクターにも登録していないただのクラス
export class UnknownLogger {
log() {
return '';
}
}
@Component({})
export class FooComponent implements OnInit {
message: string = null;
constructor(
@Optional() public logger: UnknownLogger,
) { }
ngOnInit() {
if (!this.logger) {
return;
};
// logger が null のためこの行は実行されない
this.message = this.logger.log();
}
}
@Optional
は FooComponent
の注入に対する指定のため、子コンポーネントから同じトークンに対して注入が要求され依存解決できない場合、例外が発生します。
サンプルコード/ツリー探索のチューニング
@Optional
https://github.com/ringtail003/learn-angular-di/tree/03-parameters-host-optional
@Self
ツリー探索はせず、自身の所有するトークンのみ探します。
@Component({
providers: [
{ provide: LoggerService, useValue: { log: () => 'foo component logger' } }
],
})
export class FooComponent implements OnInit {
message: string = null;
constructor(
@Self() public logger: LoggerService,
) { }
ngOnInit() {
this.message = this.logger.log(); // 'foo component logger'
}
}
@Self
は FooComponent
の注入に対する指定のため、子コンポーネントから同じトークンに対して注入が要求されると、ルートインジェクターまで遡る事になります。
サンプルコード/ツリー探索のチューニング
@Self
https://github.com/ringtail003/learn-angular-di/tree/03-parameters-host-self
@SkipSelf
自身をスキップしてツリー探索をスタートします。
自身がトークンを所有していても無視されます。
@Component({
providers: [
{ provide: LoggerService, useValue: { log: () => 'foo component logger' } }
],
})
export class FooComponent implements OnInit {
message: string = null;
constructor(
@SkipSelf() public logger: LoggerService,
) { }
ngOnInit() {
this.message = this.logger.log(); // 'root injector logger'
}
}
@SkipSelf
は FooComponent
の注入に対する指定のため、子コンポーネントから同じトークンに対して注入が要求されると FooComponent
の エレメントインジェクターは探索対象になります。
サンプルコード/ツリー探索のチューニング
@SkipSelf
https://github.com/ringtail003/learn-angular-di/tree/03-parameters-host-skipself
@Host
ツリー探索の終端である事を示します。
子コンポーネントからスタートしたツリー探索も終了します。
@Component({
providers: [
{ provide: LoggerService, useValue: { log: () => 'foo component logger' } }
],
})
export class FooComponent implements OnInit {
message: string = null;
constructor(
@Host() public logger: LoggerService,
) { }
ngOnInit() {
this.message = this.logger.log(); // 'foo component logger'
}
}
サンプルコード
https://github.com/ringtail003/learn-angular-di/tree/03-parameters-host-host
@Self
と @Host
の違い
ツリー探索が自身を終端とする事は同じですが、子コンポーネントからスタートしたツリー探索の扱いに差が出ます。
@Self
は @Optional
と併用すると 自身がトークンを所有していなければスキップし上位のインジェクターに探索を委譲 という扱いになりますが @Host
の場合は ツリー探索の終端であり必ずトークンを所有している ものとして扱われます。
@Component({
template: `<bar></bar>`
})
export class FooComponent implements OnInit {
constructor(
@Optional() @Self() public logger: UnknownLogger,
) {
if (!this.logger) {
return; // 自身が UnknownLogger を知らないので this.logger === null になる
}
}
@Component({
template: `<baz></baz>`
})
export class BarComponent implements OnInit {
constructor(
@Optional() public logger: LoggerService,
) {
this.logger; // not null: ツリー探索はルートインジェクターに到達
}
@Component({...})
export class BazComponent implements OnInit {
constructor(
@Optional() public logger: LoggerService,
) {
this.logger; // not null: ツリー探索はルートインジェクターに到達
}
カスタムトークン
アプリケーション固有の定数などは InjectionToken
を利用してトークンを付与する事で、注入可能な依存オブジェクトにする事ができます。
const appConfig = {
title: 'my application',
};
type AppConfig = typeof appConfig;
export const APP_CONFIG = new InjectionToken<AppConfig>('AppConfig', {
providedIn: 'root',
factory: () => appConfig,
});
@Component({...})
class FooComponent {
constructor(
@Inject(APP_CONFIG) appConfig: AppConfig,
) {
appConfig; // { title: 'my application' }
}
}
または下記のように型情報を依存オブジェクトとし、注入時に値のバリエーションを持たせる事もできます。
interface UiConfig {
theme: string;
}
export const UI_CONFIG = new InjectionToken<UiConfig>('UiConfig');
@Component({
providers: [
{ provide: UI_CONFIG, useValue: { theme: 'dark' } },
],
})
@Component({
providers: [
{ provide: UI_CONFIG, useValue: { theme: 'pastel' } },
],
})
InjectionToken の第一引数は依存解決に失敗した時のエラーメッセージに埋め込まれるため、一意に識別できるテキストを設定しましょう。
export const APP_CONFIG = new InjectionToken<AppConfig>('AppConfig', {});
NullInjectorError:
StaticInjectorError(AppModule)[InjectionToken app config]:
サンプルコード/カスタムトークン
https://github.com/ringtail003/learn-angular-di/tree/04-custom-token
ここまでのまとめ
- サービスクラスや定数は
@Injectable
@NgModule
@Component
@Inject
を使って依存オブジェクトとしてコンテナに登録する事ができます - 注入を要求するとインジェクターツリーからトークンを管理するインジェクターが探索されます
- ツリー探索の経路は
@Self
@SkipSelf
などを使ってチューニングする事ができます - トークンを管理するインジェクターが見つかると
providers
viewProviders
で指定された方法に従い、依存オブジェクトを注入します
参照のチューニング
Angular では一度注入された依存オブジェクトはキャッシュされ、別の場所から同じ注入が要求された場合、生成済みのインスタンスの参照が注入されます。
これがどのように動いているのかをいくつかの例で紹介します。
例として登場するコンポーネントは親子関係を示すために
foo > bar > baz
のように階層構造になっています。
<foo>
<bar>
<baz></baz>
</bar>
</foo>
モジュールインジェクターからの注入
@Injectable({ providedIn: 'root' })
export class RootService { name = 'by root'; }
// foo
@Component({ providers: [] }})
export class FooComponent {
constructor(public root: RootService) {}
}
// bar
@Component({ providers: [] }})
export class BarComponent {
constructor(public root: RootService) {}
}
// baz
@Component({ providers: [] }})
export class BazComponent {
constructor(public root: RootService) {}
}
コンポーネント | 注入を行うインジェクター |
---|---|
<foo> |
ルートのモジュールインジェクター |
<bar> |
ルートのモジュールインジェクター |
<baz> |
ルートのモジュールインジェクター |
<foo>
<bar>
<baz>
は同じインスタンスの参照を共有 しているため RootService
に変更を加えると相互に干渉します。
親コンポーネントのエレメントインジェクターからの注入
@Injectable({ providedIn: 'root' })
export class FooService { name = 'by foo'; }
// foo
@Component(
{ providers: [ FooService ] }, // 注入の指定
)
export class FooComponent {
constructor(public foo: FooService) {}
}
// bar
@Component({ providers: [] }})
export class BarComponent {
constructor(public foo: FooService) {}
}
// baz
@Component({ providers: [] }})
export class BazComponent {
constructor(public foo: FooService) {}
}
コンポーネント | 注入を行うインジェクター |
---|---|
<foo> |
foo のエレメントインジェクター |
<bar> |
foo のエレメントインジェクター |
<baz> |
foo のエレメントインジェクター |
子孫コンポーネントである <bar>
<baz>
には生成済みのインスタンスの参照が注入されるため、結果として <foo>
<bar>
<baz>
は 同じインスタンスの参照を共有 します。
子孫コンポーネントのエレメントインジェクターからの注入
@Injectable({ providedIn: 'root' })
export class BazService { name = 'by baz'; }
// foo
@Component({ providers: [] }})
export class FooComponent {
constructor(public baz: BazService) {}
}
// bar
@Component({ providers: [] }})
export class BarComponent {
constructor(public baz: BazService) {}
}
// baz
@Component(
{ providers: [ BazService ] }, // 注入の指定
)
export class BazComponent {
constructor(public baz: BazService) {}
}
コンポーネント | 注入を行うインジェクター |
---|---|
<foo> |
ルートのモジュールインジェクター |
<bar> |
ルートのモジュールインジェクター |
<baz> |
baz のエレメントインジェクター |
<foo>
<bar>
は同じインスタンスの参照を共有しますが <baz>
は 別のインスタンスを参照 します。
<baz><baz>
<baz><baz>
<baz><baz>
このようにいくつも <baz>
を配置した場合、エレメントインジェクターは 毎回新しいインスタンスを生成し注入 するため <baz>
の中で BazService
に変更を加えても他のコンポーネントに干渉しません。
useClass, useFactory, useValue の違い
ここまでの <foo>
<bar>
<baz>
の例はいずれも useClass を使ったものです。では useFactory や useValue に変更するとどうなるでしょうか?
手元で試したところ、結果はほぼ同じで 依存を解決するインジェクターがインスタンス生成やファクトリ関数を実行し、それをキャッシュする という事が分かりました。
ただし useValue を指定し同じコンポーネントを複数配置した時だけ異なる結果が得られました。
// baz
export class Isolate extends BazService {
name = 'by baz';
}
@Component(
{ provide: BazService, useValue: new Isolate },
)
export class BazComponent {
constructor(public baz: Isolate) {}
}
インジェクターは useValue に与えられた参照をそのまま注入するだけなので <baz></baz><baz></baz>
のようにいくつも <baz>
を配置した場合 同じインスタンスの参照を共有 します。結果 <baz>
の中で BazService
に変更を加えると相互に干渉します。
@Inject
での注入
const sharedToken = { name: 'by root' };
export type SharedToken = typeof sharedToken;
export const SHARED_TOKEN = new InjectionToken<SharedToken>('SharedToken', {
providedIn: 'root',
factory: () => ({ name: 'by root' }),
});
// foo
@Component({ providers: [] }})
export class FooComponent {
constructor(@Inject(SHARED_TOKEN) public shared: SharedToken) {}
}
// bar
@Component({ providers: [] }})
export class BarComponent {
constructor(@Inject(SHARED_TOKEN) public shared: SharedToken) {}
}
// baz
@Component(
{ providers: [] },
)
export class BazComponent {
constructor(@Inject(SHARED_TOKEN) public shared: SharedToken) {}
}
コンポーネント | 注入を行うインジェクター |
---|---|
<foo> |
ルートのモジュールインジェクター |
<bar> |
ルートのモジュールインジェクター |
<baz> |
ルートのモジュールインジェクター |
カスタムトークンを宣言し @Inject
で注入を要求した場合はルートのモジュールインジェクターが注入を行います。
<foo>
<bar>
<baz>
は同じ値の参照を共有 するため SharedToken
に変更を加えると相互に干渉します。
サンプルコード
注入した依存オブジェクトに変更を加えるとどのように干渉し合うのか、簡単に試せるようにサンプルコードを用意していますので、興味のある方は手元で試してみてください。
サンプルコード/参照のチューニング
useClass
https://github.com/ringtail003/learn-angular-di/tree/05-refs-class
useFactory
https://github.com/ringtail003/learn-angular-di/tree/05-refs-factory
useValue
https://github.com/ringtail003/learn-angular-di/tree/05-refs-value
おわりに
Angular の DI は多機能な分、理解して使いこなすのに時間がかかりますね。けれども一度じっくり向き合って挙動を理解するとパワフルな味方になってくれます。ぜひとも DI を味方に付けて、使いこなしていきましょう。
この記事では「こう書いたら、こう動く」の視点で Angular DI をまとめました。次のステップとして「注入のやり方っていろいろあるけど、どれを選択したらいいのか?」という悩みが出てくると思います。その解を見出す私のお薦めは、らこさんの Angular After Tutorial です。ぜひ読んでみてください。
Angular After Tutorial プロバイダーの種類と選び方
https://gitbook.lacolaco.net/angular-after-tutorial/season-4-dependency-injection/provider-types-and-choice
思い違いや表現の間違いがあった場合はコメントでご指摘いただけると嬉しいです。クッソ長い記事をここまで読んでいただき、ありがとうございました。
それでは明日の @shioyang さんにバトンを託します!