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