【超解説】Angular で子コンポーネントの @Input 変更を購読:サンプルコード付き

2024-06-20

Angular で子コンポーネントの @Input の変更を購読する方法

そこで今回は、子コンポーネントが親コンポーネントから受け取った @Input プロパティの変更を購読する方法について、分かりやすく解説します。

ngOnChanges ライフサイクルフックの利用

最も一般的な方法は、子コンポーネントの ngOnChanges ライフサイクルフックを使用する方法です。このフックは、コンポーネントのプロパティが変更されるたびに呼び出されます。

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

@Component({
  selector: 'app-child',
  template: `
    <p>親コンポーネントから受け取った値:{{ inputValue }}</p>
  `,
})
export class ChildComponent implements OnChanges {
  @Input() inputValue: string;

  ngOnChanges(changes: SimpleChanges) {
    if (changes.inputValue) {
      console.log('inputValue が変更されました! 新しい値:', this.inputValue);
      // 変更された値に基づいて処理を行う
    }
  }
}

上記の例では、inputValue プロパティが変更された際に ngOnChanges フックが呼び出され、コンソールログを出力しています。また、コメント部分には、変更された値に基づいて具体的な処理を行うための記述例が示されています。

@ViewChild と ElementRef の利用

別の方法として、@ViewChild デコレータと ElementRef を使用する方法があります。この方法では、子コンポーネントのテンプレート内の特定の要素を参照し、その要素のプロパティやイベントを監視することで、@Input プロパティの変更を検知することができます。

import { Component, Input, ViewChild, ElementRef } from '@angular/core';

@Component({
  selector: 'app-child',
  template: `
    <input #inputValueElement type="text" [(ngModel)]="inputValue">
  `,
})
export class ChildComponent {
  @Input() inputValue: string;

  @ViewChild('inputValueElement') inputValueElementRef: ElementRef;

  ngAfterViewInit() {
    this.inputValueElementRef.nativeElement.addEventListener('input', () => {
      console.log('inputValue が変更されました! 新しい値:', this.inputValue);
      // 変更された値に基づいて処理を行う
    });
  }
}

上記の例では、#inputValueElement ディレクティブを使用して inputValue プロパティと双方向バインディングを行っている <input> 要素を inputValueElementRef に参照し、ngAfterViewInit ライフサイクルフック内でその要素の input イベントにリスナーを追加しています。

RxJS を利用

より高度な方法として、RxJS ライブラリを利用する方法があります。RxJS を使用すると、Observable を用いて @Input プロパティの変更を非同期的に監視することができます。

import { Component, Input, Observable, fromEvent } from '@angular/core';
import { map } from 'rxjs/operators';

@Component({
  selector: 'app-child',
  template: `
    <p>親コンポーネントから受け取った値:{{ inputValue }}</p>
  `,
})
export class ChildComponent {
  @Input() inputValue$: Observable<string>;

  ngOnInit() {
    this.inputValue$.pipe(
      map((value) => {
        console.log('inputValue が変更されました! 新しい値:', value);
        // 変更された値に基づいて処理を行う
        return value;
      })
    ).subscribe();
  }
}

上記の例では、inputValue$ プロパティを Observable<string> 型として定義し、親コンポーネントから発行される inputValue の変更イベントを購読しています。map オペレーターを使用して、変更された値をログ出力し、処理を行うようにしています。

補足

今回紹介した方法は、それぞれ異なるユースケースに適しています。

  • ngOnChanges: シンプルな変更検知に適しています。
  • @ViewChild と ElementRef: 特定の要素のイベントを監視する必要がある場合に適しています。
  • RxJS: より複雑



ngOnChanges ライフサイクルフックの利用

// 親コンポーネント (app.component.ts)
import { Component } from '@angular/core';

@Component({
  selector: 'app-root',
  template: `
    <app-child [inputValue]="inputValue"></app-child>
    <button (click)="changeInputValue()">入力値を変更</button>
  `,
})
export class AppComponent {
  inputValue = '初期値';

  changeInputValue() {
    this.inputValue = '新しい値';
  }
}

// 子コンポーネント (app-child.component.ts)
import { Component, Input, OnChanges } from '@angular/core';

@Component({
  selector: 'app-child',
  template: `
    <p>親コンポーネントから受け取った値:{{ inputValue }}</p>
  `,
})
export class ChildComponent implements OnChanges {
  @Input() inputValue: string;

  ngOnChanges(changes: SimpleChanges) {
    if (changes.inputValue) {
      console.log('inputValue が変更されました! 新しい値:', this.inputValue);
      // 変更された値に基づいて処理を行う
    }
  }
}

@ViewChild と ElementRef の利用

// 親コンポーネント (app.component.ts)
import { Component } from '@angular/core';

@Component({
  selector: 'app-root',
  template: `
    <app-child></app-child>
    <button (click)="changeInputValue()">入力値を変更</button>
  `,
})
export class AppComponent {
  changeInputValue() {
    this.childComponent.inputValue = '新しい値';
  }

  @ViewChild(ChildComponent) childComponent: ChildComponent;
}

// 子コンポーネント (app-child.component.ts)
import { Component, Input, ViewChild, ElementRef } from '@angular/core';

@Component({
  selector: 'app-child',
  template: `
    <input #inputValueElement type="text" [(ngModel)]="inputValue">
  `,
})
export class ChildComponent {
  @Input() inputValue: string;

  @ViewChild('inputValueElement') inputValueElementRef: ElementRef;

  ngAfterViewInit() {
    this.inputValueElementRef.nativeElement.addEventListener('input', () => {
      console.log('inputValue が変更されました! 新しい値:', this.inputValue);
      // 変更された値に基づいて処理を行う
    });
  }
}

RxJS を利用

// 親コンポーネント (app.component.ts)
import { Component, Observable, Subject } from '@angular/core';
import { fromEvent } from 'rxjs';
import { map } from 'rxjs/operators';

@Component({
  selector: 'app-root',
  template: `
    <app-child [inputValue$]="inputValue$"></app-child>
    <button (click)="changeInputValue()">入力値を変更</button>
  `,
})
export class AppComponent {
  inputValue$ = new Subject<string>();

  changeInputValue() {
    this.inputValue$.next('新しい値');
  }
}

// 子コンポーネント (app-child.component.ts)
import { Component, Input, Observable } from '@angular/core';
import { map } from 'rxjs/operators';

@Component({
  selector: 'app-child',
  template: `
    <p>親コンポーネントから受け取った値:{{ inputValue }}</p>
  `,
})
export class ChildComponent {
  @Input() inputValue$: Observable<string>;

  ngOnInit() {
    this.inputValue$.pipe(
      map((value) => {
        console.log('inputValue が変更されました! 新しい値:', value);
        // 変更された値に基づいて処理を行う
        return value;
      })
    ).subscribe();
  }
}

上記のサンプルコードは、それぞれの方法における基本的な実装例です。実際の使用例では、必要に応じてロジックや処理内容を拡張することができます。

注意事項

  • サンプルコードはあくまでも一例であり、状況に応じて適宜修正する必要があります。
  • RxJS を利用する場合は、RxJS の基本的な知識が必要となります。

このサンプルコードが、Angular における子コンポーネントの @Input の変更を




Angular で子コンポーネントの @Input の変更を購読する方法:その他の方法

Input プロパティを EventEmitter に変換する

親コンポーネントで @Input プロパティを EventEmitter に変換し、子コンポーネントでそのイベントを購読する方法があります。

// 親コンポーネント (app.component.ts)
import { Component, Output, EventEmitter } from '@angular/core';

@Component({
  selector: 'app-root',
  template: `
    <app-child [inputValue]="inputValue"></app-child>
    <button (click)="changeInputValue()">入力値を変更</button>
  `,
})
export class AppComponent {
  @Input() inputValue = '初期値';

  @Output() inputValueChange = new EventEmitter<string>();

  changeInputValue() {
    this.inputValue = '新しい値';
    this.inputValueChange.emit(this.inputValue);
  }
}

// 子コンポーネント (app-child.component.ts)
import { Component, Input, Output, EventEmitter } from '@angular/core';

@Component({
  selector: 'app-child',
  template: `
    <p>親コンポーネントから受け取った値:{{ inputValue }}</p>
  `,
})
export class ChildComponent {
  @Input() inputValue: string;

  @Output() inputValueChange = new EventEmitter<string>();

  ngOnInit() {
    this.inputValueChange.subscribe((newValue) => {
      console.log('inputValue が変更されました! 新しい値:', newValue);
      // 変更された値に基づいて処理を行う
    });
  }
}

この方法の利点は、子コンポーネントが親コンポーネントに対して能動的にイベントを発信できることです。

双方向バインディングを使用する

ngModel ディレクティブなどの双方向バインディング機能を利用する方法もあります。

// 親コンポーネント (app.component.ts)
import { Component } from '@angular/core';

@Component({
  selector: 'app-root',
  template: `
    <app-child [(inputValue)]="inputValue"></app-child>
    <button (click)="changeInputValue()">入力値を変更</button>
  `,
})
export class AppComponent {
  inputValue = '初期値';

  changeInputValue() {
    this.inputValue = '新しい値';
  }
}

// 子コンポーネント (app-child.component.ts)
import { Component, Input } from '@angular/core';

@Component({
  selector: 'app-child',
  template: `
    <input type="text" [(ngModel)]="inputValue">
  `,
})
export class ChildComponent {
  @Input() inputValue: string;
}

この方法の利点は、コードが簡潔になることです。

カスタムデコレータを使用する

@Input デコレータの機能を拡張するカスタムデコレータを作成する方法もあります。

この方法は、より高度なユースケースや複雑なロジックが必要な場合に適しています。

サービスを使用する

親コンポーネントと子コンポーネント間で共有するデータをサービスに格納し、子コンポーネントがサービスを介してデータの変更を検知する方法もあります。

この方法は、複数のコンポーネント間でデータを共有する必要がある場合に適しています。

それぞれの方法の比較

方法利点欠点適している状況
ngOnChangesシンプル複雑なロジックには不向き基本的な変更検知
@ViewChildElementRef特定の要素のイベントを監視できるコードが煩雑になる可能性がある特定の要素の変更を検知する必要がある場合
RxJS非同期処理に適している学習曲線がやや高い複雑なロジックや非同期処理が必要な場合
EventEmitter子コンポーネントが能動的にイベントを発信できるコードが冗長になる可能性がある子コンポーネントが親コンポーネントに対してアクションを通知する必要がある場合
双方向バインディングコードが簡潔データフローが複雑になる可能性があるシンプルなデータバインディング
カスタムデコレータ柔軟

angular


Angular vs React vs Vue:フロントエンドフレームワーク徹底比較

言語:AngularJSはJavaScriptベースです。アーキテクチャ:AngularJSは、Model-View-Controller (MVC) アーキテクチャに基づいています。Angularは、コンポーネントベースのアーキテクチャに基づいています。...


Angular CLIで生成されるspec.tsファイルの役割

spec. tsファイルとはspec. tsファイルは、単体テスト用のファイルです。単体テストとは、個々のコンポーネントやサービスなど、アプリケーションの小さな部分を独立してテストする方法です。spec. tsファイルには、以下の内容が含まれます。...


RouterEvent ハンドラーを使って Angular でナビゲーションをキャンセルする

CanActivate ロガード:説明: CanActivate ロガードは、ルートへのアクセスを許可するかどうかを制御するために使用されます。ナビゲーションが開始される前に呼び出され、ガードが false を返すとナビゲーションがキャンセルされます。...


Angular MatPaginator の初期化:3 つの方法と詳細解説

Angular Material の MatPaginator コンポーネントが初期化されないという問題は、多くの開発者を悩ませるよくある問題です。この問題は、様々な要因によって引き起こされる可能性があり、根本的な原因を特定して解決することが重要です。...


Angular 8 の static オプションでコンポーネントテンプレートから直接子要素を参照する方法

従来、@ViewChild デコレータは、コンポーネントクラスのメンバー変数に子要素の参照を格納するために使用されていました。この方法では、@ViewChild デコレータはコンポーネントクラスのメンバー変数に子要素の参照を格納するため、コンポーネントが初期化された後にのみ子要素にアクセスできます。...