ビューの初期化と変更に対応するngAfterViewInit、ngAfterVieweChecked
ngAfterViewInitは、コンポーネントの画面(ビュー)が初期化されたとき、ngAfterViewCheckedはビューが変更されてAngular 2が検証を完了したタイミングで実行されます。図8のサンプル(004-afterview)で、実行の様子を説明します。
このサンプルを実行すると、画面表示時点でngAfterViewInitとngAfterViewCheckedが実行されます。また、テキストボックスの値を変更すると、ngAfterViewCheckedがその都度実行されます。
このサンプルでは、まずリスト6の子コンポーネント(ChildComponent)を利用します。このコンポーネントには1個のテキストボックスと、それに対応するchildInputプロパティが存在します。
@Component({
selector: "lifecycle-child"
template: `
<div class="component">
<h4>ChildComponent</h4>
childInput: <input type="text" [(ngModel)]="childInput">
</div>
`,
})
export class ChildComponent {
// テキストボックスへの入力値
childInput:string="初期値";
}
子コンポーネントを参照するコンポーネントの実装はリスト7のようになります。
export class LifeCycleComponent
implements AfterViewInit, AfterViewChecked {
// 子コンポーネント ...(1)
@ViewChild(ChildComponent) viewChild: ChildComponent;
// 画面に表示する文字列 ...(2)
valueFromChild:string="";
// View初期化後に呼ばれる ...(3)
ngAfterViewInit() {
console.debug("ngAfterViewInit:" + this.viewChild.childInput);
}
// View更新ごとに呼ばれる ...(4)
ngAfterViewChecked() {
console.debug("ngAfterViewChecked:" + this.viewChild.childInput);
this.displayValueFromChild();
}
// 子コンポーネントのchildInputの値を画面に表示する ...(5)
displayValueFromChild() {
// この時点でビューの処理が終わっているので、実行を遅らせる ...(6)
if (this.valueFromChild != this.viewChild.childInput) {
setTimeout(() => {
this.valueFromChild=this.viewChild.childInput;
}, 0);
}
// このようにすると、入力がすぐに画面に反映されない ...(7)
// this.valueFromChild = this.viewChild.childInput;
}
}
(1)の@ViewChildデコレーターで、子コンポーネントをviewChild変数で参照できるようにしています。(2)はデータバインディングで画面表示する文字列変数です。(3)と(4)がライフサイクルフックの実装です。ここではviewChild.childInputの内容をデバッグログに出力しつつ、(4)では(5)で実装されるdisplayValueFromChildメソッドを実行しています。
displayValueFromChildメソッド(5)は、子コンポーネントのchildInputプロパティを、自身のvalueFromChildにコピーして、画面表示するメソッドです。ngAfterViewCheckedライフサイクルフックの実行時点では、ビューの処理が全て完了しているため、(6)のように、Angularが次の変更検知を行うタイミングまで、setTimeoutで画面表示処理を遅らせる必要があります。
なお、(7)のように画面表示処理を遅らせないと、図10のように、内容がすぐに画面に反映されなくなります。テキストボックスからフォーカスを外すと、Angular 2が画面の変更を検知して、入力内容を画面に反映します。
外部コンテンツの初期化と変更に対応するngAfterContentInit、ngAfterContentChecked
ngAfterViewInit、ngAfterViewCheckedがビューに関連するのに対して、外部コンテンツのライフサイクルフックが、ngAfterContentInitとngAfterContentCheckedです。
コンポーネント外部からコンテンツを取り込むContent Projection
Angular 2では、コンポーネントのタグ内に記述された外部コンテンツを、コンポーネント内に取り込む「Content projection」が利用できます(AngularJS 1の「Transclusion」に対応します)。例えば、リスト6の子コンポーネントに対応する<lifecycle-child>タグを、lifecycleコンポーネントの<lifecycle>タグ内に記述できます。
<lifecycle> <!-- 子コンポーネントの表示 --> <lifecycle-child></lifecycle-child> </lifecycle>
外部コンテンツの内容は、lifecycleコンポーネントのテンプレートに記述された<ng-content>タグの箇所に反映されます。
ngAfterContentInit、ngAfterContentCheckedは、このように記述した外部コンテンツの初期化と変更に対応するライフサイクルフックです。
ngAfterContentInit、ngAfterContentCheckedの実装例
図11のサンプル(005-aftercontent)で、ngAfterContentInit、ngAfterContentCheckedが実行される様子を説明します。
表示内容、デバッグログの出力タイミングは図8、9と似ていますが、このサンプルでは、ngAfterContentInit、ngAfterContentCheckedライフサイクルフックで処理を行います。
子コンポーネントを呼び出すコンポーネントの実装はリスト9のようになります。
export class LifeCycleComponent
implements AfterContentInit, AfterContentChecked {
// 子コンポーネント ...(1)
@ContentChild(ChildComponent) contentChild: ChildComponent;
// 画面に表示する文字列
valueFromChild:string="";
// コンテンツ初期化後に呼ばれる ...(2)
ngAfterContentInit() {
console.debug("ngAfterContentInit:" + this.contentChild.childInput);
}
// コンテンツ更新ごとに呼ばれる ...(3)
ngAfterContentChecked() {
console.debug("ngAfterContentChecked:" + this.contentChild.childInput);
this.displayValueFromChild();
}
// 子コンポーネントのchildInputの値を画面に表示する ...(4)
displayValueFromChild() {
// AfterViewフックの前なので、バインディング変数を直接変更できる ...(5)
this.valueFromChild = this.contentChild.childInput;
}
}
子コンポーネントChildComponentは、対応する<lifecycle-child>タグが外部コンテンツとして記述されているので、(1)の@ContentChildデコレーターで参照します。(2)と(3)がライフサイクルフックの実装です。画面を更新する処理(4)は、ngAfterContentCheckedの実行時点でビューのライフサイクルフックがまだ実行されていないため(図1参照)、リスト7と違って(5)のように直接画面を書き換えることができます。このように、外部コンテンツのライフサイクルフックは、ビューのライフサイクルフックより前に実行されることに注意してください。
まとめ
本記事では、Angular 2のコンポーネントで定義されているライフサイクルと、ライフサイクルフックのメソッド実装方法を説明しました。ライフサイクルを理解してライフサイクルフックを実装すれば、状態に合った適切なタイミングで処理を実行できます。
