div
この問題を解決するには、サイズ変更イベントごとにコンポーネント プロパティのサイズを取得して保存し、そのプロパティをテンプレートで使用するだけです。このようにして、変更検出の 2 回目のラウンドが開発モードで実行されるときに、値は一定のままになります。
また、テンプレートに@HostListener
追加するのではなく、使用することをお勧めします。への参照を取得するために(window:resize)
使用します。そして、ライフサイクル フックを使用して初期値を設定します。@ViewChild
div
ngAfterViewInit()
import {Component, ViewChild, HostListener} from '@angular/core';
@Component({
selector: 'my-app',
template: `<div #widgetParentDiv class="Content">
<p>Sample widget</p>
<table><tr>
<td>Value1</td>
<td *ngIf="divWidth > 350">Value2</td>
<td *ngIf="divWidth > 700">Value3</td>
</tr>
</table>`,
})
export class AppComponent {
divWidth = 0;
@ViewChild('widgetParentDiv') parentDiv:ElementRef;
@HostListener('window:resize') onResize() {
// guard against resize before view is rendered
if(this.parentDiv) {
this.divWidth = this.parentDiv.nativeElement.clientWidth;
}
}
ngAfterViewInit() {
this.divWidth = this.parentDiv.nativeElement.clientWidth;
}
}
うまくいかないのは残念です。我々が得る
チェック後に表情が変わっています。以前の値: 「false」。現在の値: 「真」。
エラーは式について不平を言っていますNgIf
-最初に実行するdivWidth
と0になり、次にngAfterViewInit()
実行して値を0以外に変更し、2回目の変更検出が実行されます(devモードで)。ありがたいことに、簡単で既知の解決策があります。これは 1 回限りの問題であり、OP のような継続的な問題ではありません。
ngAfterViewInit() {
// wait a tick to avoid one-time devMode
// unidirectional-data-flow-violation error
setTimeout(_ => this.divWidth = this.parentDiv.nativeElement.clientWidth);
}
1 ティックを待機するこの手法は、https ://angular.io/docs/ts/latest/cookbook/component-communication.html#!#parent-to-view-child で文書化されていることに注意してください。
多くの場合、これらのメソッドはコンポーネントのビューが構成された後に呼び出されるため、このトリックを使用する必要がありますngAfterViewInit()
。ngAfterViewChecked()
setTimeout()
ここに作業がありplunkerます。
これを改善することができます。サイズ変更イベントが発生するたびにではなく、たとえば 100 ~ 250 ミリ秒ごとに Angular 変更検出が実行されるように、サイズ変更イベントを調整する必要があると思います。これにより、ユーザーがウィンドウのサイズを変更しているときにアプリの動作が遅くなるのを防ぐことができます。現在、サイズ変更イベントごとに変更検出が実行されるためです (開発モードで 2 回)。これは、前のプランカーに次のメソッドを追加することで確認できます。
ngDoCheck() {
console.log('change detection');
}
オブザーバブルはイベントを簡単に調整できるため@HostListener
、サイズ変更イベントにバインドする代わりに、オブザーバブルを作成します。
Observable.fromEvent(window, 'resize')
.throttleTime(200)
.subscribe(_ => this.divWidth = this.parentDiv.nativeElement.clientWidth );
これは機能しますが...それを試しているうちに、非常に興味深いことがわかりました...サイズ変更イベントを抑制しても、サイズ変更イベントが発生するたびにAngularの変更検出が実行されます。つまり、スロットリングは変更検出の実行頻度には影響しません。(Tobias Bosch はこれを確認しました:
https://github.com/angular/angular/issues/1773#issuecomment-102078250。)
イベントがスロットル時間を経過した場合にのみ変更検出を実行したい。そして、このコンポーネントで実行する変更検出だけが必要です。解決策は、Angular ゾーンの外側にオブザーバブルを作成し、サブスクリプション コールバック内で変更検出を手動で呼び出すことです。
constructor(private ngzone: NgZone, private cdref: ChangeDetectorRef) {}
ngAfterViewInit() {
// set initial value, but wait a tick to avoid one-time devMode
// unidirectional-data-flow-violation error
setTimeout(_ => this.divWidth = this.parentDiv.nativeElement.clientWidth);
this.ngzone.runOutsideAngular( () =>
Observable.fromEvent(window, 'resize')
.throttleTime(200)
.subscribe(_ => {
this.divWidth = this.parentDiv.nativeElement.clientWidth;
this.cdref.detectChanges();
})
);
}
ここに作業がありplunkerます。
plunker に、counter
ライフサイクル フックを使用してすべての変更検出サイクルをインクリメントする を追加しましたngDoCheck()
。このメソッドが呼び出されていないことがわかります。カウンター値はサイズ変更イベントで変化しません。
detectChanges()
このコンポーネントとその子に対して変更検出を実行します。ルート コンポーネントから変更検出を実行する (つまり、完全な変更検出チェックを実行する) 場合は、ApplicationRef.tick()
代わりに使用します (これは plunker でコメント化されています)。が呼び出されるtick()
ことに注意してください。ngDoCheck()
これは素晴らしい質問です。さまざまなソリューションを試すのに多くの時間を費やし、多くのことを学びました。この質問を投稿していただきありがとうございます。