Dev Study
← 解説「output() — 親へイベントを通知する」に戻る

サンプルコードで身につける: output() — 親へイベントを通知する

解説で学んだ概念を、5つの具体例で確認します。素朴な例から実務寄りの例へと進みます。

1「押された」ことだけを親へ知らせる

output の最小例で、値を持たない出来事は output<void>() と宣言して引数なしの emit() で発火します。子は「閉じるが押された」と知らせるだけで、実際に何を閉じるかは親が決めます。モーダルやバナーの閉じるボタンがまさにこの形です。

import { Component, output } from '@angular/core';

@Component({
  selector: 'app-close-button',
  template: '<button (click)="notifyClose()">閉じる</button>',
})
export class CloseButtonComponent {
  // 値が不要な通知は output<void>() と宣言する
  closed = output<void>();

  notifyClose() {
    this.closed.emit(); // 「押された」ことだけを親へ知らせる
  }
}

// 親側: <app-close-button (closed)="hideDialog()" />

2数値を添えて通知する

output<number>() と宣言すると、emit(1) のように値を添えて通知できます。どのボタンが押されたかを値の違いで親へ伝える、投票ボタンの例です。親は $event でこの値を受け取り、スコアの加算といった処理を行います。

import { Component, output } from '@angular/core';

@Component({
  selector: 'app-vote-buttons',
  template:
    '<button (click)="vote(1)">+1</button>' +
    '<button (click)="vote(-1)">-1</button>',
})
export class VoteButtonsComponent {
  voted = output<number>();

  vote(point: number) {
    this.voted.emit(point); // 押されたボタンに応じた値を親へ渡す
  }
}

// 親側: <app-vote-buttons (voted)="addScore($event)" />

3オブジェクトをまとめて通知する

通知したい情報が複数あるときは、型を定義してオブジェクトごと emit します。「どの列を・昇順か降順か」という並び替えの指示を1回の通知にまとめる例です。通知の中身に型が付くので、親側の受け取り処理でもプロパティ名の間違いをコンパイラが見つけてくれます。

import { Component, output } from '@angular/core';

type SortChange = { key: string; ascending: boolean };

@Component({
  selector: 'app-sort-header',
  template: '<button (click)="toggle()">価格順で並び替え</button>',
})
export class SortHeaderComponent {
  sortChanged = output<SortChange>();
  ascending = true;

  toggle() {
    this.ascending = !this.ascending;
    // 複数の情報は型付きオブジェクトにまとめて通知する
    this.sortChanged.emit({ key: 'price', ascending: this.ascending });
  }
}

4親が通知を受け取って状態を変える

子の output と、それを受け取る親を1つのコードで見せる例です。子は答えを emit するだけで自分では何も保存せず、状態の変更は親の onAnswered に集約されています。「データの変更箇所を親に集める」という output 設計の狙いが、この親子のペアで具体的に見えます。

import { Component, output, signal } from '@angular/core';

@Component({
  selector: 'app-agree-buttons',
  template:
    '<button (click)="answer(true)">はい</button>' +
    '<button (click)="answer(false)">いいえ</button>',
})
export class AgreeButtonsComponent {
  answered = output<boolean>();
  answer(yes: boolean) {
    this.answered.emit(yes); // 子は通知するだけで何も保存しない
  }
}

@Component({
  selector: 'app-survey',
  imports: [AgreeButtonsComponent],
  template:
    '<app-agree-buttons (answered)="onAnswered($event)" />' +
    '<p>回答: {{ result() }}</p>',
})
export class SurveyComponent {
  result = signal('未回答');
  onAnswered(yes: boolean) {
    this.result.set(yes ? '受け取る' : '受け取らない'); // 変更は親に集約
  }
}

5再利用できる検索バー部品を作る

入力とEnter・ボタン押下をまとめ、確定したキーワードだけを親へ emit する検索バーの実務例です。検索の実行(API呼び出しなど)は親に任せるので、この部品は商品検索でもユーザー検索でもそのまま使い回せます。「見た目と操作は子、処理は親」という分業の完成形です。

import { Component, output } from '@angular/core';

@Component({
  selector: 'app-search-bar',
  template:
    '<input (input)="onInput($event)" (keyup.enter)="submit()" placeholder="検索">' +
    '<button (click)="submit()">検索</button>',
})
export class SearchBarComponent {
  searched = output<string>();
  private draft = '';

  onInput(e: Event) {
    this.draft = (e.target as HTMLInputElement).value;
  }

  submit() {
    this.searched.emit(this.draft); // 検索の実行は親に任せる
  }
}

// 親側: <app-search-bar (searched)="fetchResults($event)" />