序章
デフォルトでは、Angular 2+は、アプリで何かが変更されるたびに、すべてのコンポーネント(上から下)で変更検出を実行します。 変更は、ユーザーイベントまたはネットワーク要求から受信したデータから発生する可能性があります。
変更の検出は非常にパフォーマンスが高いですが、アプリがより複雑になり、コンポーネントの数が増えるにつれて、変更の検出はますます多くの作業を実行する必要があります。
1つの解決策は、特定のコンポーネントにOnPush変更検出戦略を使用することです。 これにより、Angularは、データが変更されたときではなく、新しい参照がコンポーネントに渡されたときにのみ、これらのコンポーネントとそのサブツリーで変更検出を実行するように指示されます。
この記事では、ChangeDetectionStrategyとChangeDetectorRefについて学習します。
前提条件
この記事をフォローしたい場合は、次のものが必要になります。
- Angularコンポーネントにある程度精通していると役立つ場合があります。
- この記事では、 RxJSライブラリも参照しており、
BehaviorSubjectおよびObservableに精通していることも役立つ場合があります。
ChangeDetectionStrategyの例を調べる
水生生物のリストを表示し、ユーザーがリストに新しい生物を追加できるようにする子コンポーネントを持つサンプルコンポーネントを調べてみましょう。
app.component.ts
import { Component } from '@angular/core';
@Component({
selector: 'app-root',
templateUrl: './app.component.html'
})
export class AppComponent {
aquaticCreatures = ['shark', 'dolphin', 'octopus'];
addAquaticCreature(newAquaticCreature) {
this.aquaticCreatures.push(newAquaticCreature);
}
}
そして、テンプレートは次のようになります。
app.component.html
<input #inputAquaticCreature type="text" placeholder="Enter a new creature"> <button (click)="addAquaticCreature(inputAquaticCreature.value)">Add creature</button> <app-child [data]="aquaticCreatures"></app-child>
app-childコンポーネントは次のようになります。
child.component.ts
import { Component, Input } from '@angular/core';
@Component({
selector: 'app-child',
templateUrl: './child.component.html'
})
export class ChildComponent {
@Input() data: string[];
}
また、app-childテンプレートは次のようになります。
child.component.html
<ul>
<li *ngFor="let item of data">{{ item }}</li>
</ul>
コンパイルしてブラウザでアプリケーションにアクセスした後、shark、dolphin、およびoctopusを含む順序付けされていないリストを確認する必要があります。
水生生物を入力フィールドに入力し、生物の追加ボタンをクリックすると、新しい生物がリストに追加されます。
Angularが親コンポーネントでデータが変更されたことを検出すると、子コンポーネントが更新されます。
次に、子コンポーネントの変更検出戦略をOnPushに設定しましょう。
child.component.ts
import { Component, Input, ChangeDetectionStrategy } from '@angular/core';
@Component({
selector: 'app-child',
templateUrl: './child.component.html',
changeDetection: ChangeDetectionStrategy.OnPush
})
export class ChildComponent {
@Input() data: string[];
}
再コンパイルしてブラウザでアプリケーションにアクセスした後、shark、dolphin、およびoctopusを含むソートされていないリストを確認する必要があります。
ただし、新しい水生生物を追加しても、順序付けられていないリストに追加されていないようです。 新しいデータは引き続き親コンポーネントのaquaticCreatures配列にプッシュされますが、Angularはデータ入力の新しい参照を認識しないため、コンポーネントで変更検出を実行しません。
データ入力への新しい参照を渡すには、Array.pushをaddAquaticCreatureのspread構文(...)に置き換えることができます。
app.component.ts
// ...
addAquaticCreature(newAquaticCreature) {
this.aquaticCreatures = [...this.aquaticCreatures, newAquaticCreature];
}
// ...
このバリエーションでは、aquaticCreaturesアレイを変更しなくなりました。 完全に新しい配列を返しています。
再コンパイル後、アプリケーションが以前と同じように動作することを確認する必要があります。 Angularはdataへの新しい参照を検出したため、子コンポーネントで変更検出を実行しました。
これで、OnPush変更検出戦略を使用するようにサンプルの親コンポーネントと子コンポーネントを変更することができます。
ChangeDetectorRefの例を調べる
OnPushの変更検出戦略を使用する場合、何かが変更されるたびに新しい参照を渡すようにする以外に、ChangeDetectorRefを使用して完全に制御することもできます。
ChangeDetectorRef.detectChanges()
たとえば、データを変更し続けてから、子コンポーネントにRefreshボタンのあるボタンを設定できます。
これには、Array.pushを使用するためにaddAquaticCreatureを元に戻す必要があります。
app.component.ts
// ...
addAquaticCreature(newAquaticCreature) {
this.aquaticCreatures.push(newAquaticCreature);
}
// ...
そして、refresh()をトリガーするbutton要素を追加します。
child.component.html
<ul>
<li *ngFor="let item of data">{{ item }}</li>
</ul>
<button (click)="refresh()">Refresh</button>
次に、ChangeDetectorRefを使用するように子コンポーネントを変更します。
child.component.ts
import {
Component,
Input,
ChangeDetectionStrategy,
ChangeDetectorRef
} from '@angular/core';
@Component({
selector: 'app-child',
templateUrl: './child.component.html',
changeDetection: ChangeDetectionStrategy.OnPush
})
export class ChildComponent {
@Input() data: string[];
constructor(private cd: ChangeDetectorRef) {}
refresh() {
this.cd.detectChanges();
}
}
コンパイルしてブラウザでアプリケーションにアクセスした後、shark、dolphin、およびoctopusを含む順序付けされていないリストを確認する必要があります。
配列に新しいアイテムを追加しても、順序付けされていないリストは更新されません。 ただし、更新ボタンを押すと、コンポーネントの変更検出が実行され、更新が実行されます。
ChangeDetectorRef.markForCheck()
データ入力が実際に観測可能であるとしましょう。
この例では、RxJSBehaviorSubjectを使用します。
app.component.ts
import { Component } from '@angular/core';
import { BehaviorSubject } from 'rxjs';
@Component({
selector: 'app-root',
templateUrl: './app.component.html'
})
export class AppComponent {
aquaticCreatures = new BehaviorSubject(['shark', 'dolphin', 'octopus']);
addAcquaticCreature((newAquaticCreature) {
this.aquaticCreatures.next(newAquaticCreature);
}
}
そして、子コンポーネントのOnInitフックでサブスクライブします。
ここで、水生生物をaquaticCreatures配列に追加します。
child.component.ts
import {
Component,
Input,
ChangeDetectionStrategy,
ChangeDetectorRef,
OnInit
} from '@angular/core';
import { Observable } from 'rxjs';
@Component({
selector: 'app-child',
templateUrl: './child.component.html',
changeDetection: ChangeDetectionStrategy.OnPush
})
export class ChildComponent implements OnInit {
@Input() data: Observable<any>;
aquaticCreatures: string[] = [];
constructor(private cd: ChangeDetectorRef) {}
ngOnInit() {
<^>this.data.subscribe(newAquaticCreature => {
this.aquaticCreatures = [...this.aquaticCreatures, ...newAquaticCreature];
});
}
}
新しいデータがdataオブザーバブルを変更するため、このコードは完全ではありません。そのため、Angularは変更検出を実行しません。 解決策は、オブザーバブルをサブスクライブするときにChangeDetectorRefのmarkForCheckを呼び出すことです。
child.component.ts
// ...
ngOnInit() {
this.data.subscribe(newAquaticCreature => {
this.aquaticCreatures = [...this.aquaticCreatures, ...newAquaticCreature];
this.cd.markForCheck();
});
}
// ...
markForCheckは、この特定の入力が変更されたときに変更検出をトリガーする必要があることをAngularに指示します。
ChangeDetectorRef.detach()およびChangeDetectorRef.reattach()
ChangeDetectorRefで実行できるもう1つの強力な方法は、detachおよびreattachメソッドを使用して、変更検出を手動で完全に切り離して再接続することです。
結論
この記事では、ChangeDetectionStrategyとChangeDetectorRefを紹介しました。 デフォルトでは、Angularはすべてのコンポーネントで変更検出を実行します。 ChangeDetectionStrategyおよびChangeDetectorRefをコンポーネントに適用して、データが変更された場合と比較して、新しい参照の変更検出を実行できます。
Angularについて詳しく知りたい場合は、Angularトピックページで演習とプログラミングプロジェクトを確認してください。