導入
@Input / @Output は「親子の間」でデータを受け渡す仕組みでした。でも実務では、親子でもない離れたコンポーネントどうしで、同じデータや処理を共有したくなります(ログイン中のユーザー情報、買い物カゴ、サーバー通信…)。
そこで Angular の心臓部が登場します。それが サービス(共通ロジックの置き場)と、それを部品に配る仕組み 依存性注入(DI: Dependency Injection) です。「Angular の中心的な考え方」と言われるのがこれです。
説明
考え方はシンプルです。共有したいものを「サービス」というクラス 1 つにまとめ、必要なコンポーネントがそれを inject() で受け取るだけ。サービスは(既定で)アプリ全体にたった 1 つだけ作られ、みんなが同じものを見ます。
flowchart TB svc["CounterService(アプリに1つだけ)<br/>count / increment()"] a["AppComponent<br/>ボタンで +1 する"] b["DisplayComponent ①<br/>数を表示"] c["DisplayComponent ②<br/>数を表示"] svc -->|"inject() で受け取る"| a svc -->|"inject() で受け取る"| b svc -->|"inject() で受け取る"| c
@Injectable({ providedIn: 'root' }) を付けたクラスがサービスです。providedIn: 'root' は「アプリ全体で共有する 1 つを用意して」という意味です。
// 共有したいデータとロジックをまとめたサービス
@Injectable({ providedIn: 'root' })
class CounterService {
count = 0;
increment(): void {
this.count++;
}
}
// 子:サービスを inject して、数を表示するだけ
@Component({
selector: 'app-display',
standalone: true,
template: `<p>表示コンポーネント: {{ counter.count }}</p>`,
})
class DisplayComponent {
counter = inject(CounterService); // 同じ1つを受け取る
}
// 親:ボタンで increment を呼ぶ。子を2つ並べる。
@Component({
selector: 'app-root',
standalone: true,
imports: [DisplayComponent],
template: `
<h2>サービスで状態を共有</h2>
<button (click)="counter.increment()">+1</button>
<p>ボタン側: {{ counter.count }}</p>
<app-display></app-display>
<app-display></app-display>
`,
})
class AppComponent {
counter = inject(CounterService);
}
ボタンを押すと、親も、2 つの子も、全部いっしょに数が増えます。3 つのコンポーネントが inject(CounterService) で受け取っているのは、すべて同じ 1 つのサービスだからです。@Input で配線しなくても、データが共有できました。
@Injectable({ providedIn: 'root' })… このクラスをアプリ共通のサービスとして登録する印。inject(CounterService)… 「その共有の 1 つをください」と Angular にお願いする書き方。Angular が自動で渡してくれる(=依存性の”注入”)。- サービスに
countのようなデータや、increment()のような処理、将来はHttpClientによるサーバー通信もまとめられます。
やってみよう
CounterServiceにreset(): void { this.count = 0; }を足し、AppComponentに「リセット」ボタンを付けて、全員が 0 に戻ることを確かめましょう。<app-display>を 3 つ 4 つと増やしても、全部が同期することを見てみましょう。