要解决您的问题,您只需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
通常,ngAfterViewInit()
我们ngAfterViewChecked()
需要使用这个setTimeout()
技巧,因为这些方法是在组件的视图组合之后调用的。
这是一个工作plunker。
我们可以做得更好。我认为我们应该限制调整大小事件,以便 Angular 更改检测仅运行一次,例如,每 100-250 毫秒,而不是每次发生调整大小事件时。这应该可以防止应用程序在用户调整窗口大小时变得迟缓,因为现在,每个调整大小事件都会导致更改检测运行(在开发模式下两次)。您可以通过将以下方法添加到先前的 plunker 来验证这一点:
ngDoCheck() {
console.log('change detection');
}
Observables 可以轻松地限制事件,因此我们将创建一个 observable,而不是使用@HostListener
绑定到 resize 事件:
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 区域之外创建 observable,然后在订阅回调中手动调用更改检测:
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()
被调用。
这是一个很好的问题。我花了很多时间尝试不同的解决方案,我学到了很多东西。感谢您发布这个问题。