BehaviorSubject/ReplaySubjectで@Input()値の変化を検知する
Angularで@Input()値の変化を検知する方法
ここでは、以下の3つの方法について解説します。
- ngOnChangesライフサイクルフックを使用する
- @Input()デコレータにsetterを追加する
- BehaviorSubject/ReplaySubjectを使用する
ngOnChangesライフサイクルフックを使用する
Angularは、コンポーネントの入力プロパティが変更された際にngOnChanges
ライフサイクルフックを呼び出します。このフック内で、previousValue
とcurrentValue
を比較することで、値の変化を検知できます。
import { Component, OnInit, OnChanges, Input } from '@angular/core';
@Component({
selector: 'my-child',
templateUrl: './child.component.html',
})
export class ChildComponent implements OnInit, OnChanges {
@Input() value: number;
previousValue: number;
ngOnChanges(changes: SimpleChanges) {
if (changes['value']) {
this.previousValue = changes['value'].previousValue;
// 現在の値と前回の値を比較して処理を行う
}
}
constructor() {}
ngOnInit() {}
}
この方法はシンプルですが、以下の点に注意が必要です。
- すべての入力プロパティの変化を検知したい場合、
ngOnChanges
内で個別に処理する必要があります。 - 深い階層のオブジェクトや配列の場合、変更箇所を特定するのが複雑になる場合があります。
@Input()デコレータにsetterを追加することで、値が変更されるたびに呼び出され、変更後の値を受け取ることができます。
import { Component, OnInit, Input } from '@angular/core';
@Component({
selector: 'my-child',
templateUrl: './child.component.html',
})
export class ChildComponent implements OnInit {
@Input() set value(newValue: number) {
// 現在の値と前回の値を比較して処理を行う
this.previousValue = this._value;
this._value = newValue;
}
private _value: number;
constructor() {}
ngOnInit() {}
}
この方法は、ngOnChanges
ライフサイクルフックを使用するよりも簡潔に記述できます。ただし、setter内で複雑な処理を行う場合は、コードの見通しが悪くなる可能性があります。
RxJSのBehaviorSubject/ReplaySubjectを使用することで、値の変化をストリームとして処理できます。
import { Component, OnInit, Input } from '@angular/core';
import { BehaviorSubject } from 'rxjs';
@Component({
selector: 'my-child',
templateUrl: './child.component.html',
})
export class ChildComponent implements OnInit {
@Input() value: number;
private valueSubject = new BehaviorSubject<number>(this.value);
constructor() {}
ngOnInit() {
this.valueSubject.subscribe((value) => {
// 現在の値と前回の値を比較して処理を行う
});
}
}
この方法は、複雑な処理や複数のコンポーネント間で値を共有したい場合に有効です。
上記3つの方法の中から、要件に合った方法を選択してください。
ngOnChangesライフサイクルフックを使用する
import { Component, OnInit, OnChanges, Input } from '@angular/core';
@Component({
selector: 'my-parent',
templateUrl: './parent.component.html',
})
export class ParentComponent implements OnInit {
value = 1;
constructor() {}
ngOnInit() {}
changeValue() {
this.value++;
}
}
@Component({
selector: 'my-child',
templateUrl: './child.component.html',
})
export class ChildComponent implements OnInit, OnChanges {
@Input() value: number;
previousValue: number;
ngOnChanges(changes: SimpleChanges) {
if (changes['value']) {
this.previousValue = changes['value'].previousValue;
console.log(`親コンポーネントから受け取った値が${this.previousValue}から${this.value}に変更されました`);
}
}
constructor() {}
ngOnInit() {}
}
@Input()デコレータにsetterを追加する
import { Component, OnInit, Input } from '@angular/core';
@Component({
selector: 'my-parent',
templateUrl: './parent.component.html',
})
export class ParentComponent implements OnInit {
value = 1;
constructor() {}
ngOnInit() {}
changeValue() {
this.value++;
}
}
@Component({
selector: 'my-child',
templateUrl: './child.component.html',
})
export class ChildComponent implements OnInit {
@Input() set value(newValue: number) {
console.log(`親コンポーネントから受け取った値が${this._value}から${newValue}に変更されました`);
this._value = newValue;
}
private _value: number;
constructor() {}
ngOnInit() {}
}
BehaviorSubject/ReplaySubjectを使用する
import { Component, OnInit, Input } from '@angular/core';
import { BehaviorSubject } from 'rxjs';
@Component({
selector: 'my-parent',
templateUrl: './parent.component.html',
})
export class ParentComponent implements OnInit {
value = 1;
constructor() {}
ngOnInit() {}
changeValue() {
this.value++;
}
}
@Component({
selector: 'my-child',
templateUrl: './child.component.html',
})
export class ChildComponent implements OnInit {
@Input() value: number;
private valueSubject = new BehaviorSubject<number>(this.value);
constructor() {}
ngOnInit() {
this.valueSubject.subscribe((value) => {
console.log(`親コンポーネントから受け取った値が${this._value}から${value}に変更されました`);
this._value = value;
});
}
}
実行方法
- 上記のサンプルコードを
index.html
とapp.component.ts
ファイルに保存します。 ng serve
コマンドを実行してアプリケーションを起動します。- ブラウザで
http://localhost:4200
を開きます。 - "親コンポーネントの値を変更"ボタンをクリックすると、コンソールに値の変化が出力されます。
Angularで@Input()値の変化を検知するその他の方法
ngDoCheck
ライフサイクルフックは、コンポーネントがレンダリングされるたびに呼び出されます。このフック内で、@Input
プロパティの値を前回の値と比較することで、変化を検知できます。
import { Component, OnInit, OnChanges, Input, DoCheck } from '@angular/core';
@Component({
selector: 'my-child',
templateUrl: './child.component.html',
})
export class ChildComponent implements OnInit, DoCheck {
@Input() value: number;
previousValue: number;
ngDoCheck() {
if (this.value !== this.previousValue) {
this.previousValue = this.value;
// 現在の値と前回の値を比較して処理を行う
}
}
constructor() {}
ngOnInit() {}
}
この方法は、ngOnChanges
ライフサイクルフックよりも簡潔に記述できます。ただし、ngDoCheck
はコンポーネントがレンダリングされるたびに呼び出されるため、パフォーマンスに影響を与える可能性があります。
ViewChild
とContentChild
デコレータを使用することで、子コンポーネントのインスタンスを取得できます。取得したインスタンスの@Input
プロパティを直接参照することで、値の変化を検知できます。
import { Component, OnInit, Input, ViewChild, ContentChild } from '@angular/core';
@Component({
selector: 'my-parent',
templateUrl: './parent.component.html',
})
export class ParentComponent implements OnInit {
value = 1;
constructor() {}
ngOnInit() {}
changeValue() {
this.value++;
}
}
@Component({
selector: 'my-child',
templateUrl: './child.component.html',
})
export class ChildComponent implements OnInit {
@Input() value: number;
constructor() {}
ngOnInit() {}
}
@Component({
selector: 'my-app',
templateUrl: './app.component.html',
})
export class AppComponent implements OnInit {
@ViewChild(ChildComponent) childComponent: ChildComponent;
constructor() {}
ngOnInit() {
this.childComponent.value; // 子コンポーネントの@Input()プロパティにアクセス
}
}
この方法は、子コンポーネントのインスタンスを直接操作するため、コードが複雑になる可能性があります。
MutationObserverは、DOMの変化を監視するAPIです。このAPIを使用することで、@Input
プロパティが変更された際に、その変化を検知できます。
import { Component, OnInit, Input } from '@angular/core';
import { MutationObserver } from '@angular/cdk/platform-browser';
@Component({
selector: 'my-child',
templateUrl: './child.component.html',
})
export class ChildComponent implements OnInit {
@Input() value: number;
constructor() {}
ngOnInit() {
const observer = new MutationObserver((mutations) => {
mutations.forEach((mutation) => {
if (mutation.type === 'attributes' && mutation.attributeName === 'value') {
// 現在の値と前回の値を比較して処理を行う
}
});
});
observer.observe(this.elementRef.nativeElement, {
attributes: true,
});
}
}
この方法は、DOM操作に依存するため、コードが複雑になる可能性があります。
上記の方法の中から、要件とパフォーマンスを考慮して、最適な方法を選択してください。
angular angular2-changedetection angular-decorator