導入
第4章のTODOアプリ(レッスン10)は、「入力フォーム」と「一覧表示」が1つのコンポーネントに同居していました。実務のアプリでは、この2つは別々のコンポーネントに分かれることがほとんどです。それでも同じデータを見て、同じように更新できる必要があります。サービス(レッスン11)を使えば、配列のようなまとまったデータでも簡単に共有できます。
説明
レッスン11の CounterService は数値ひとつでした。今回は配列を持つ TodoService を作り、「入力フォーム用のコンポーネント」と「一覧表示用のコンポーネント」の2つに分けて、同じサービスを共有させます。
flowchart TB svc["TodoService(アプリに1つだけ)<br/>items配列 / add() / remove()"] form["TodoFormComponent<br/>入力欄と追加ボタン"] list["TodoListComponent<br/>一覧表示と削除ボタン"] form -->|"todo.add(text)"| svc svc -->|"todo.items"| list
@Injectable({ providedIn: 'root' })
class TodoService {
items: string[] = ['牛乳を買う'];
add(text: string): void {
if (text.trim() === '') return;
this.items.push(text);
}
remove(i: number): void {
this.items.splice(i, 1);
}
}
@Component({
selector: 'app-todo-form',
standalone: true,
template: `
<input [value]="text" (input)="text = $any($event.target).value" placeholder="やること" />
<button (click)="submit()">追加</button>
`,
})
class TodoFormComponent {
todo = inject(TodoService);
text = '';
submit(): void {
this.todo.add(this.text);
this.text = '';
}
}
@Component({
selector: 'app-todo-list',
standalone: true,
imports: [CommonModule],
template: `
<ul>
<li *ngFor="let item of todo.items; let i = index">
{{ item }} <button (click)="todo.remove(i)">✕</button>
</li>
</ul>
<p>残り {{ todo.items.length }} 件</p>
`,
})
class TodoListComponent {
todo = inject(TodoService);
}
@Component({
selector: 'app-root',
standalone: true,
imports: [TodoFormComponent, TodoListComponent],
template: `
<h2>やることリスト(フォームと一覧を分離)</h2>
<app-todo-form></app-todo-form>
<app-todo-list></app-todo-list>
`,
})
class AppComponent {}
TodoFormComponentはinject(TodoService)で共有サービスを受け取り、add(text)を呼ぶだけ。一覧の描画のことは何も知りません。TodoListComponentも同じサービスを受け取り、itemsを*ngForで表示するだけ。追加のことは何も知りません。- 2つのコンポーネントは互いの存在を知らないのに、同じ
TodoServiceを見ているので動きが噛み合います。@Input/@Output で親子をつなぐ必要がありません。
やってみよう
TodoServiceにclear(): void { this.items = []; }を追加し、どちらかのコンポーネントに「全部消す」ボタンを付けて動作を確かめましょう。<app-todo-form>をテンプレートに2つ並べても、<app-todo-list>の一覧はちゃんと1つに揃うことを確認してみましょう。