Ant*_*Day 40 javascript responsive-design angular2-changedetection angular
我已经对这个错误进行了一些阅读和调查,但不确定我的情况的正确答案是什么.我知道在开发模式下,更改检测运行两次,但我不愿意enableProdMode()
用来掩盖问题.
下面是一个简单的示例,其中表格中的单元格数量应随着div的宽度扩展而增加.(请注意,div的宽度不仅仅是屏幕宽度的函数,因此不能轻易应用@Media)
我的HTML看起来如下(widget.template.html):
<div #widgetParentDiv class="Content">
<p>Sample widget</p>
<table><tr>
<td>Value1</td>
<td *ngIf="widgetParentDiv.clientWidth>350">Value2</td>
<td *ngIf="widgetParentDiv.clientWidth>700">Value3</td>
</tr></table>
Run Code Online (Sandbox Code Playgroud)
这本身没有任何作用.我猜这是因为没有任何因素导致发生变化检测.但是,当我将第一行更改为以下内容并创建一个空函数来接收调用时,它开始工作,但偶尔我得到'表达式在检查后已更改错误'
<div #widgetParentDiv class="Content">
gets replaced with
<div #widgetParentDiv (window:resize)=parentResize(10) class="Content">
Run Code Online (Sandbox Code Playgroud)
我最好的猜测是,通过这种修改,触发更改检测并且一切都开始响应,但是,当宽度快速变化时抛出异常,因为前一次更改检测迭代需要更长的时间来完成,而不是更改div的宽度.
有关我的项目的更多详细信息,请参阅此类似问题.
谢谢
Mar*_*cok 65
要解决您的问题,您只需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;
}
}
Run Code Online (Sandbox Code Playgroud)
太糟糕了,不起作用.我们得到了
检查后表情发生了变化.上一个值:'false'.当前值:'true'.
错误是抱怨我们的NgIf
表达式 - 第一次运行时,divWidth
为0,然后ngAfterViewInit()
运行并将值更改为0以外的值,然后运行第2轮更改检测(在开发模式下).值得庆幸的是,有一个简单/已知的解决方案,这是一次性问题,而不是OP中的持续问题:
ngAfterViewInit() {
// wait a tick to avoid one-time devMode
// unidirectional-data-flow-violation error
setTimeout(_ => this.divWidth = this.parentDiv.nativeElement.clientWidth);
}
Run Code Online (Sandbox Code Playgroud)
请注意,这种等待一个滴答的技术在此处记录:https://angular.io/docs/ts/latest/cookbook/component-communication.html#!# parent-to-view- child
通常情况下,在ngAfterViewInit()
和ngAfterViewChecked()
我们需要使用的setTimeout()
伎俩,因为组件的视图是由后被调用这些方法.
这是一个工作plunker.
我们可以做得更好.我认为我们应该限制调整大小事件,以便Angular变化检测仅运行,例如,每100-250ms运行一次,而不是每次调整resize事件.这可以防止应用程序在用户调整窗口大小时变得迟缓,因为现在,每个调整大小事件都会导致更改检测运行(在开发模式下两次).您可以通过将以下方法添加到上一个plunker来验证这一点:
ngDoCheck() {
console.log('change detection');
}
Run Code Online (Sandbox Code Playgroud)
Observable可以轻松地限制事件,因此@HostListener
我们将创建一个observable ,而不是使用绑定到resize事件:
Observable.fromEvent(window, 'resize')
.throttleTime(200)
.subscribe(_ => this.divWidth = this.parentDiv.nativeElement.clientWidth );
Run Code Online (Sandbox Code Playgroud)
这很有效,但是......在尝试这个时,我发现了一些非常有趣的东西......即使我们调整了resize事件,每当有一个resize事件时,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();
})
);
}
Run Code Online (Sandbox Code Playgroud)
这是一个工作plunker.
在plunker中我添加了一个counter
,我使用生命周期钩子递增每个更改检测周期ngDoCheck()
.您可以看到此方法未被调用 - 计数器值在调整大小事件时不会更改.
detectChanges()
将对此组件及其子组件运行更改检测.如果您希望从根组件运行更改检测(即,运行完整更改检测检查),则使用ApplicationRef.tick()
(在plunker中注释掉).请注意,这tick()
将导致ngDoCheck()
被调用.
这是一个很好的问题.我花了很多时间尝试不同的解决方案,并且学到了很多东西.感谢您发布此问题.
小智 15
我用来解决这个问题的其他方式:
import { Component, ChangeDetectorRef } from '@angular/core';
@Component({
selector: 'your-seelctor',
template: 'your-template',
})
export class YourComponent{
constructor(public cdRef:ChangeDetectorRef) { }
ngAfterViewInit() {
this.cdRef.detectChanges();
}
}
Run Code Online (Sandbox Code Playgroud)
归档时间: |
|
查看次数: |
57265 次 |
最近记录: |