gpt4 book ai didi

javascript - Angular2 - 检查后表达式已更改 - 通过调整大小事件绑定(bind)到 div 宽度

转载 作者:IT王子 更新时间:2023-10-29 03:09:15 24 4
gpt4 key购买 nike

我已经对这个错误做了一些阅读和调查,但不确定我的情况的正确答案是什么。我知道在开发模式下,更改检测会运行两次,但我不愿意使用 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>

这本身什么都不做。我猜这是因为没有任何东西导致发生变化检测。但是,当我将第一行更改为以下内容并创建一个空函数来接收调用时,它开始工作,但偶尔我会收到“检查错误后表达式已更改”

<div #widgetParentDiv class="Content">
gets replaced with
<div #widgetParentDiv (window:resize)=parentResize(10) class="Content">

我最好的猜测是,通过此修改,会触发更改检测并且所有内容都开始响应,但是,当宽度快速变化时会抛出异常,因为上一次更改检测的迭代比更改 div 的宽度需要更长的时间才能完成.

  1. 是否有更好的方法来触发变化检测?
  2. 我是否应该通过函数捕获调整大小事件以确保发生变化检测?
  3. 正在使用#widthParentDiv 访问div 的宽度可以接受吗?
  4. 是否有更好的整体解决方案?

有关我的项目的更多详细信息,请参阅 this类似的问题。

谢谢

最佳答案

要解决您的问题,您只需在每次调整大小事件后获取 div 的大小并将其存储在组件属性中,然后在模板中使用该属性。这样,当第二轮变化检测在开发模式下运行时,该值将保持不变。

我还建议使用 @HostListener 而不是将 (window:resize) 添加到您的模板中。我们将使用 @ViewChild 来获取对 div 的引用。我们将使用生命周期钩子(Hook) 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;
}
}

太糟糕了,这不起作用。我们得到

Expression has changed after it was checked. Previous value: 'false'. Current value: 'true'.

错误是提示我们的 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() 技巧,因为这些方法被调用在组成组件的 View 之后。

这是一个有效的 plunker .


我们可以做得更好。我认为我们应该限制调整大小事件,以便 Angular 变化检测仅每 100-250 毫秒运行一次,而不是每次发生调整大小事件时运行。这应该可以防止应用程序在用户调整窗口大小时变得迟钝,因为现在,每个调整大小事件都会导致更改检测运行(在开发模式下两次)。您可以通过在之前的 plunker 中添加以下方法来验证这一点:

ngDoCheck() {
console.log('change detection');
}

Observable 可以很容易地限制事件,因此我们将创建一个 observable,而不是使用 @HostListener 绑定(bind)到调整大小事件:

Observable.fromEvent(window, 'resize')
.throttleTime(200)
.subscribe(_ => this.divWidth = this.parentDiv.nativeElement.clientWidth );

这行得通,但是......在试验时,我发现了一些非常有趣的东西......即使我们限制了调整大小事件,每次发生调整大小事件时,Angular 变化检测仍然会运行。即,节流不会影响更改检测的运行频率。 (托比亚斯博世证实了这一点: 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,我使用生命周期钩子(Hook) ngDoCheck() 递增每个更改检测周期。你可以看到这个方法没有被调用——计数器值不会在调整大小事件中改变。

detectChanges()将对此组件及其子组件运行更改检测。如果您更愿意从根组件运行更改检测(即运行完整的更改检测检查),则使用 ApplicationRef.tick()相反(这在 plunker 中被注释掉了)。请注意,tick() 将导致调用 ngDoCheck()


这是一个很好的问题。我花了很多时间尝试不同的解决方案,并且学到了很多东西。感谢您提出这个问题。

关于javascript - Angular2 - 检查后表达式已更改 - 通过调整大小事件绑定(bind)到 div 宽度,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/38930183/

24 4 0
Copyright 2021 - 2024 cfsdn All Rights Reserved 蜀ICP备2022000587号
广告合作:1813099741@qq.com 6ren.com