本文へスキップ
BecomeCoder

Angularコース · 第5章 サービスとSignals · レッスン13

サービスにデータをためる ― 複数の部品で共有する

ブラウザで完結

導入

第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 {}
  • TodoFormComponentinject(TodoService) で共有サービスを受け取り、add(text) を呼ぶだけ。一覧の描画のことは何も知りません。
  • TodoListComponent も同じサービスを受け取り、items*ngFor で表示するだけ。追加のことは何も知りません。
  • 2つのコンポーネントは互いの存在を知らないのに、同じ TodoService を見ているので動きが噛み合います。@Input/@Output で親子をつなぐ必要がありません。

やってみよう

  • TodoServiceclear(): void { this.items = []; } を追加し、どちらかのコンポーネントに「全部消す」ボタンを付けて動作を確かめましょう。
  • <app-todo-form> をテンプレートに2つ並べても、<app-todo-list> の一覧はちゃんと1つに揃うことを確認してみましょう。

実際に動かしてみよう

下のエディタにAngularのコンポーネントを書くと、右側のプレビューにその場で描画されます。本文の例を打ち込んだり書き換えたりして、テンプレート構文やバインディングの動きを確かめながら進めましょう(初回だけ読み込みに少し時間がかかります)。

Angular — ブラウザ内で実行

ブラウザ内でAngular(JIT)を動かす学習用環境(同梱)を読み込みます(初回のみ少し時間がかかります)。
スクロールして表示された時点でも自動で読み込まれます。