要解决您的问题,您只需获取并存储div
在每个调整大小事件之后的组件属性中,并在模板中使用该属性。这样,当第二轮变化检测在开发模式下运行时,该值将保持不变。
我也推荐使用@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;
}
}
可惜这不起作用。我们得到
检查后表情发生了变化。先前值:“假”。当前值:“真”。
错误是抱怨我们的NgIf
表达式——第一次运行时,divWidth
是 0,那么ngAfterViewInit()
运行并将值更改为 0 以外的值,然后运行第二轮更改检测(在开发模式下)。值得庆幸的是,有一个简单/已知的解决方案,这是一个一次性的问题,而不是像OP中那样的持续问题:
ngAfterViewInit() {
// wait a tick to avoid one-time devMode
// unidirectional-data-flow-violation error
setTimeout(_ => this.divWidth = this.parentDiv.nativeElement.clientWidth);
}
请注意,这里记录了这种等待一个周期的技术:https://angular.io/docs/ts/latest/cookbook/component-communication.html#!#parent-to-view-child https://angular.io/docs/ts/latest/cookbook/component-communication.html#!#parent-to-view-child
常常,在ngAfterViewInit()
and ngAfterViewChecked()
我们需要雇用setTimeout()
这是一个技巧,因为这些方法是在组件视图组成之后调用的。
Here's a working plunker http://plnkr.co/edit/1XhUD6jQ4otDx12LsN5b?p=preview.
我们可以让这变得更好。我认为我们应该限制调整大小事件,以便角度变化检测仅每 100-250 毫秒运行一次,而不是每次发生调整大小事件时运行。这应该可以防止用户调整窗口大小时应用程序变得缓慢,因为现在每个调整大小事件都会导致更改检测运行(在开发模式下运行两次)。您可以通过将以下方法添加到之前的 plunker 中来验证这一点:
ngDoCheck() {
console.log('change detection');
}
Observables 可以轻松地限制事件,所以不要使用@HostListener
为了绑定到调整大小事件,我们将创建一个可观察的:
Observable.fromEvent(window, 'resize')
.throttleTime(200)
.subscribe(_ => this.divWidth = this.parentDiv.nativeElement.clientWidth );
这是可行的,但是......在进行实验时,我发现了一些非常有趣的东西......即使我们限制了调整大小事件,每次发生调整大小事件时,角度变化检测仍然会运行。即,限制不会影响更改检测的运行频率。 (托比亚斯·博斯证实了这一点:https://github.com/angular/angular/issues/1773#issuecomment-102078250 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();
})
);
}
Here's a working plunker http://plnkr.co/edit/sMSIz2kKN5NKvN2s6rC8?p=preview.
在 plunker 中我添加了一个counter
我使用生命周期钩子增加每个变更检测周期ngDoCheck()
。您可以看到该方法没有被调用——计数器值在调整大小事件时不会改变。
detectChanges() https://angular.io/docs/ts/latest/api/core/index/ChangeDetectorRef-class.html#!#detectChanges-anchor将对此组件及其子组件运行更改检测。如果您希望从根组件运行更改检测(即运行完整的更改检测检查),则使用ApplicationRef.tick() https://angular.io/docs/ts/latest/api/core/index/ApplicationRef-class.html#!#tick-anchor相反(这在 plunker 中被注释掉了)。注意tick()
会引发ngDoCheck()
被称为。
这是一个很好的问题。我花了很多时间尝试不同的解决方案,并且学到了很多东西。感谢您发布这个问题。