Angular2 - 表达式在检查后发生了变化 - 使用resize事件绑定到div宽度 [英] Angular2 - Expression has changed after it was checked - Binding to div width with resize events

查看:201
本文介绍了Angular2 - 表达式在检查后发生了变化 - 使用resize事件绑定到div宽度的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我已经对这个错误进行了一些阅读和调查,但不确定我的情况的正确答案是什么。我知道在开发模式下,更改检测运行两次,但我不愿意使用 enableProdMode()来掩盖问题。

I have done some reading and investigation on this error, but not sure what the correct answer is for my situation. I understand that in dev mode, change detection runs twice, but I am reluctant to use enableProdMode() to mask the issue.

这是一个简单的例子,表格中的单元格数量应该随着div的宽度扩展而增加。 (请注意,div的宽度不仅仅是屏幕宽度的函数,因此无法轻松应用@Media)

Here is a simple example where the number of cells in the table should increase as the width of the div expands. (Note that the width of the div is not a function of just the screen width, so @Media cannot easily be applied)

我的HTML如下所示(widget.template .html):

My HTML looks as follows (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>

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

This on its own does nothing. I'm guessing this is because nothing is causing change detection to occur. However, when I change the first line to the following, and create an empty function to receive the call, it starts working, but occasionally I get the 'Expression has changed after it was checked error'

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

我最好的猜测是,通过此修改,触发更改检测并且一切都开始响应,但是,由于上一次更改检测迭代所需的时间比更改div的宽度要长,因此宽度会快速变化,因为前一次更改检测迭代需要更长的时间。

My best guess is that with this modification, change detection is triggered and everything starts responding, however, when the width changes rapidly the exception is thrown because the previous iteration of change detection took longer to complete than changing the width of the div.


  1. 是有更好的方法来触发变化检测吗?

  2. 我是否应该通过函数捕获resize事件以确保发生
    更改检测?

  3. 使用#widthParentDiv来访问可接受的div的
    宽度?

  4. 有更好的整体解决方案吗?

  1. Is there a better approach to triggering the change detection?
  2. Should I be capturing the resize event through a function to ensure change detection occurs?
  3. Is using #widthParentDiv to access the width of the div acceptable?
  4. Is there a better overall solution?

有关我项目的更多详情,请参阅< a href =https://stackoverflow.com/questions/38518016/angular2-respond-to-div-size>这个类似的问题。

For more details on my project please see this similar question.

谢谢

推荐答案

要解决您的问题,您只需要获取并存储 div <的大小每个resize事件后的组件属性中的/ code>,并在模板中使用该属性。这样,当第二轮更改检测以开发模式运行时,该值将保持不变。

To solve your issue, you simply need to get and store the size of the div in a component property after each resize event, and use that property in the template. This way, the value will stay constant when the 2nd round of change detection runs in dev mode.

我还建议使用 @HostListener 而不是将(window:resize)添加到您的模板中。我们将使用 @ViewChild 来获取对 div 的引用。我们将使用生命周期钩子 ngAfterViewInit()来设置初始值。

I also recommend using @HostListener rather than adding (window:resize) to your template. We'll use @ViewChild to get a reference to the div. And we'll use lifecycle hook ngAfterViewInit() to set the initial value.

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;
  }
}

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

Too bad that doesn't work. We get


表达式在检查后发生了变化。上一个值:'false'。当前值:'true'。

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

错误是抱怨我们的 NgIf 表达式 - 第一次运行时, divWidth 为0,然后 ngAfterViewInit()运行并将值更改为某个值除了0之外,第二轮更改检测运行(在开发模式下)。值得庆幸的是,有一个简单/已知的解决方案,这是一次性问题,而不是OP中的持续问题:

The error is complaining about our NgIf expressions -- the first time it runs, divWidth is 0, then ngAfterViewInit() runs and changes the value to something other than 0, then the 2nd round of change detection runs (in dev mode). Thankfully, there is an easy/known solution, and this is a one-time only issue, not a continuing issue like in the 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

Note that this technique, of waiting one tick is documented here: https://angular.io/docs/ts/latest/cookbook/component-communication.html#!#parent-to-view-child

通常,在 ngAfterViewInit() ngAfterViewChecked()我们需要使用 setTimeout()技巧,因为这些方法被调用在组件的视图组成之后。

Often, in ngAfterViewInit() and ngAfterViewChecked() we'll need to employ the setTimeout() trick because these methods are called after the component's view is composed.

这是一个有效的 plunker

Here's a working plunker.

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

We can make this better. I think we should throttle the resize events such that Angular change detection only runs, say, every 100-250ms, rather then every time a resize event occurs. This should prevent the app from getting sluggish when the user is resizing the window, because right now, every resize event causes change detection to run (twice in dev mode). You can verify this by adding the following method to the previous plunker:

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

Observable可以轻松限制事件,因此不使用 @HostListener 绑定到resize事件,我们将创建一个observable:

Observables can easily throttle events, so instead of using @HostListener to bind to the resize event, we'll create an observable:

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

这很有效,但是......在尝试时,我发现了一些非常有趣的东西......甚至虽然我们限制了resize事件,但每次有resize事件时,Angular变化检测仍会运行。即,限制不会影响更改检测的运行频率。 (Tobias Bosch证实了这一点:$ b​​ $ b https://github.com/angular/angular/issues/ 1773#issuecomment-102078250 。)

This works, but... while experimenting with that, I discovered something very interesting... even though we throttle the resize event, Angular change detection still runs every time there is a resize event. I.e., the throttling does not affect how often change detection runs. (Tobias Bosch confirmed this: https://github.com/angular/angular/issues/1773#issuecomment-102078250.)

我只希望在事件超过节流时间时运行变化检测。而且我只需要在这个组件上运行更改检测。解决方案是在Angular区域外创建observable,然后在订阅回调中手动调用change detection:

I only want change detection to run if the event passes the throttle time. And I only need change detection to run on this component. The solution is to create the observable outside the Angular zone, then manually call change detection inside the subscription callback:

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

Here's a working plunker.

在plunker中我添加了一个计数器我使用生命周期挂钩 ngDoCheck()递增每个更改检测周期。你可以看到这个方法没有被调用–调整大小事件时计数器值不会更改。

In the plunker I added a counter that I increment every change detection cycle using lifecycle hook ngDoCheck(). You can see that this method is not being called – the counter value does not change on resize events.

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

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

This is a great question. I spent a lot of time trying out different solutions and I learned a lot. Thank you for posting this question.

这篇关于Angular2 - 表达式在检查后发生了变化 - 使用resize事件绑定到div宽度的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆