Angular: 异常:表达式在检查后发生了变化。

创建于 2015-12-18  ·  147评论  ·  资料来源: angular/angular

最有用的评论

这不是错误,而是开发模式按预期工作的功能。 调用enableProdMode( ) - 在引导应用程序时查看更新的 plunk防止抛出异常。

也就是说,它被抛出是有充分理由的:简而言之,在每一轮更改检测之后,开发模式立即执行第二轮以验证自第一轮结束以来没有绑定更改,因为这表明正在引起更改通过变化检测本身。

在您的 plunk 中,您具有绑定[attr.spinner]=isLoading ,并且isLoading因您调用this.load()而发生变化,这发生在ngAfterViewInit被触发时作为初始更改检测回合的一部分发生。 不过,这本身并没有问题 - 问题在于this.load()更改了一个绑定,但不会触发新一轮的更改检测,这意味着该绑定将不会更新,直到未来的某个更改检测轮。

所有147条评论

我一直面临类似的问题,似乎是由于子项的初始化方式以及父项如何在其初始化过程中更新子项。 我有一个类似的示例,其中包含使用区域的丑陋解决方法: http :

构造函数(私有_zone:NgZone){}

ngAfterViewInit() : void {
this._zone.overrideOnTurnDone(() => {
this.child.title = '来自父母的标题';
});
}

这不是错误,而是开发模式按预期工作的功能。 调用enableProdMode( ) - 在引导应用程序时查看更新的 plunk防止抛出异常。

也就是说,它被抛出是有充分理由的:简而言之,在每一轮更改检测之后,开发模式立即执行第二轮以验证自第一轮结束以来没有绑定更改,因为这表明正在引起更改通过变化检测本身。

在您的 plunk 中,您具有绑定[attr.spinner]=isLoading ,并且isLoading因您调用this.load()而发生变化,这发生在ngAfterViewInit被触发时作为初始更改检测回合的一部分发生。 不过,这本身并没有问题 - 问题在于this.load()更改了一个绑定,但不会触发新一轮的更改检测,这意味着该绑定将不会更新,直到未来的某个更改检测轮。

我需要在afterViewInit设置我的组件,因为我在此处初始化了ChildView 。 对isLoading进行更改的正确方法是什么? 通过setTimeout包装对this.load的调用?

您只需要做一些事情,任何事情,在该方法期间触发另一轮更改检测 - 发出事件,无论如何。 将它包装在超时中(队列闪回到 ng1 :-P)是一种可行的方法,但在 ng2 中对我来说感觉非常混乱。

它看起来对用户不友好。 我认为该方法是我可以在定义其子项时完成组件初始化的地方,现在事实证明它具有一些自定义规则。 我认为您应该在内部强制执行新的更改检测轮,以便它对用户隐藏在 ng2 中。

你能在这里发布一个你想要实现的更具代表性的样本吗? 如果它是您的图像微调器,则最好使用标准绑定。

@AerisG222同上。 有什么理由不能使用简单的绑定来实现这一点吗? 通常不推荐覆盖区域行为。

@robwormald是的,在这种情况下,我可以进行属性绑定。 我的真实案例还涉及另一个更复杂的属性 - 自定义对象数组,但当然也可以通过属性绑定进行设置。

我想如果没有这样的东西@ViewChild ,我不会太在意,因为这将是传递信息的唯一合理方式。 然而,我很高兴看到@ViewChild以便我可以在代码中处理组件引用。 这简化了标记并允许通过代码进行更多控制,这对于打字稿来说也很有价值,因为有更多的工具支持(如智能感知和编译时检查)。 它还简化了容器组件,因为它不必跟踪仅用作子级状态的成员变量。

另一个小问题是,在使用属性绑定语法查看应用程序时,您必须检查模板以查看谁最终使用了数据。 当通过代码连接时,这会更明显。

我已经清理了代码以仅显示对示例而言重要的内容。 这个想法是仅在图像加载时显示微调器,然后删除微调器并显示图像。 仅当元素在屏幕上可见时才加载图像。

inflate.ts

import {Component, Input, Renderer, ElementRef, AfterViewInit, ViewChild} from 'angular2/core';

@Component({
  selector: '[inflate]',
  templateUrl: './inflate.html',
  styleUrls: ['./inflate.css']
})
export class Inflate implements AfterViewInit {
  @Input('inflate') url: string;
  @ViewChild('holder') holder: ElementRef;
  isLoaded = false;
  isLoading = false;

  constructor(private renderer: Renderer) {}

  ngAfterViewInit() {
    setTimeout(_=> this.inflate()); // BUGFIX: https://github.com/angular/angular/issues/6005#issuecomment-165911194
  }

  inflate() {
    let bounds = <ClientRect>this.holder.nativeElement.getBoundingClientRect();
    if(bounds.bottom > 0 && bounds.top < window.innerHeight) {
      this.isLoading = true;
      let img = new Image();
      img.src = this.url;
      img.onload = _=> {
        this.isLoaded = true;
        this.isLoading = false;
        this.renderer.setElementStyle(this.holder,
          'background-image', 'url("' + this.url + '")');
      };
    }
  }
}

inflate.html

<image-holder #holder></image-holder>
<spinner *ngIf="isLoading"></spinner>

我需要将图像保留在主机中,因为当它在加载完成时淡入动画时,它不应该影响主机背景不透明度。

我有一个与嵌套指令相关的潜在问题。
http://plnkr.co/edit/myX2Alx9jie2q0FDIME7?p=preview
在这里,我在 progressBar.ts 中绑定到 ngStyle。
我在返回之前记录样式对象以证明它们是相等的。
我也得到 ExpressionChangedAfterItHasBeenCheckedException 。

@svi3c你实际上是在返回一个 _different_ 对象。 每个函数调用将返回一个新的对象实例,它在键/值方面与前一个“相等”,但在实例/引用方面不相同。

有多种方法可以处理这种情况:

  • 更新一个对象实例中的值,而不是返回一个新副本
  • 使用ChangeDetectionStrategy.OnPush策略,因此您的函数仅在输入更改之一时调用,例如: http :

您可能需要检查https://github.com/ng-bootstrap/core/blob/master/src/progressbar/progressbar.ts作为 ng2 的进度条指令的另一个示例(使用引导程序的 HTML/CSS)

@pkozlowski-opensource 非常感谢!
我只阅读了 https://angular.io/docs/ts/latest/guide/template-syntax.html#!#ngstyle 并且我还没有偶然发现变更检测策略。 那太棒了! :)

@德鲁摩尔:

您只需要做一些事情,任何事情,在该方法期间触发另一轮更改检测 - 发出事件,无论如何。

您能否建议在 ngAfterViewInit 中重新触发更改检测的 _correct_ 和 _simplest_ 方法是什么 - 假设所有需要的是要更新的属性。 ApplicationRef.tick()在这里不起作用,因为这会导致“ApplicationRef.tick 被递归调用”异常; 发出一个事件只是为了触发更改检测感觉是错误的(并且对我不起作用), setTimeout

根据这里的评论,更新 ngAfterViewInit 中的绑定属性似乎是一个相当普遍的要求(由于对子组件的依赖),因此 Angular 2 似乎缺少一种简单的方法来做到这一点。

如果在 ngAfterViewInit 中更新绑定属性是_错误_的事情,那么替代方法是什么?

这是一个快速的:

挑战:
根据呈现的宽度设置 dom 元素的高度(通过某个公式)。

期待:
当元素被渲染时,获取宽度并基于它计算它的高度。

现实:
设置超时?!

  ngAfterViewInit() {
    window.setTimeout(() =>
      this.elementHeight = (this.nativeElement.offsetWidth * this.randomImage.dim.h) / this.randomImage.dim.w
    )
  }

@Birowsky Plunker?

@zoechi这是一个例子: http : //plnkr.co/edit/MML5uHEFQdL0k0TA5HtY

您可以在ngAfterViewInit生命周期挂钩中的app/app.component.ts#21中切换setTimeout

在尝试实现自定义ngModel valueAccessorwriteValue( newValue )方法时,我也遇到了类似的问题。 当我尝试将 newValue 应用于组件时,它告诉我值已更改(以一种不好的方式)。 就像你们都发现的那样,将它包装在 setTimeout() 中“修复”它; 但是,不是很好。

我还有一个例子。 它相当简单,因为@HostBinding()已设置并由QueryList通知。

与我的问题 #5950 相同。

这里有一些关于如何干净地做到这一点的建议。
http://stackoverflow.com/questions/34827334/triggering-angular2-change-detection-manually

但是,我仍然用setTimeout包装,因为我无法让它们中的任何一个做我需要的。 看起来我可以在回调结束时ChangeDetectorRef.detectChanges()并触发 CD.... 我错过了什么吗? 也许它会帮助你们中的一个人。

我正在使用 HTML5 媒体源扩展来创建基于视频元素的音频上下文,因此需要使用@ViewChild 。 基于音频上下文,我试图显示一些信息,例如通道数。 但是,我也遇到了这个问题。 绕过这个的正确方法是什么?

@ElsewhereGames在没有看到一些实际代码的情况下很难说。 你能创建一个Plunker吗

@zoechi我没有要报告的实际错误,只是说明不直接访问 DOM,虽然可取,但并不总是可以避免的(回应@robwormald评论)。 有关实际 API,请参阅createMediaElementSource上的文档。

我在一月份发回了一个 _ngModel_ 问题。 现在,我又回到了_ViewChildren_ 问题。 然而,与其他一些示例不同,我实际上没有尝试在我的演示中

http://www.bennadel.com/blog/3040-i-have-a-fundamental-misunderstanding-of-change-detection-in-angular-2-beta-8.htm

遇到类似的问题,但可以重写代码来解决它。 @bennadel 的帖子让我笑

https://github.com/angular/angular/issues/6618

到目前为止,总有办法解决它,但至少在这里再次建议更改检测机制可以使用更多文档。 @bennadel你的问题让我想起了另一个问题,

https://github.com/angular/angular/issues/5177

在哪里解决这个问题,建议添加额外的生命周期钩子 OnBeforeBinding/OnBeforeRendering。 该解决方案似乎也可以解决您的问题。

@tandu只需将ngAfterViewInit替换ngOnInit

@tandu你的微调器什么时候开始并不重要(加载子项,加载子项中的图像 - 对于用户来说,一切都在加载),只有当你需要关闭它时才重要。 您可以在“ngAfterViewInit”中使用 setTimeout 将其关闭(我同意它很丑),但这是错误的 - 子组件应该在图像加载完成后向父组件触发一个事件。 加载子组件和加载图像是 2 个不同的事件。

@e-oz https://github.com/angular/angular/issues/6005#issuecomment -165951692 我访问子边界来检测我是否应该开始图像加载。

@tandu ,您可以将所有这些检查移动到子组件代码(在子组件的 ngAfterViewInit 中是合法的)并将事件发送到父级,何时更改 isLoading。

@e-oz 我在 html 中将组件定义为#holder<image-holder #holder></image-holder> 。 所以没有它的js文件。

所以创建它,否则不需要使用ngAfterViewInit,如果View
没有真正的组件作为孩子 - 什么都不会被渲染。

在Sun,2016年3月13日在15:18 tandu [email protected]写道:

@e-oz https://github.com/e-oz我在html中将组件定义为#holder:

持有人>

. 所以没有它的js文件。


直接回复此邮件或在 GitHub 上查看
https://github.com/angular/angular/issues/6005#issuecomment -195963389。

我也可以使用element.getElementsByTagsName或类似的方法而不使用 ChildComponent。 肯定存在解决方法。 我对setTimeout修复没问题。 问题是方法名称表明它可以用于定义一些逻辑,希望子组件被初始化并绑定到组件,但该方法并没有告诉它在变更检测周期的中间运行,所以我们有很长的时间来自不同人的抱怨线。

@tandu我完全同意你的看法,这是非常烦人的限制。

对于它的价值,我已将我原来的解决方法更改为以下方法,这开始感觉像是一种合理的方法。 对于您的情况,也许这值得一试:

    constructor(private _changeDetectionRef : ChangeDetectorRef) { }

    ngAfterViewInit() : void {
        // your code

        this._changeDetectionRef.detectChanges();
    }

使用表单验证(需要输入)时,我遇到了同样的异常。 与这个问题有关吗?

Plunker: http ://plnkr.co/edit/Fp6XT5mC6ZCB14Z1gvTi?p=preview

@eivindr快速浏览了一下,我之前没有在输入中看到 ngFormControl,无法理解为什么或它意味着什么,因为它应该在表单元素上将其绑定到控制组,并且单个输入将具有相应的控制。 无论如何在你的plunker中删除

 [ngFormControl]="formModel.find('title')" 

它修复了你的错误。

我拒绝放弃这个问题 :) 昨天,我能够证明我的问题与依赖输入属性的主机绑定特别相关:

http://www.bennadel.com/blog/3056-host-bindings-are-breaking-the-ngmodel-bridge-in-angular-2-beta-11.htm

我不得不相信这是一个错误,因为组件有两个依赖于输入的东西——视图模板和主机绑定——并且_只有其中一个_导致更改异常。 如果他们俩都引起了这个问题,我会想别的; 但是,由于只有其中一个导致了问题,所以它一定是一个错误,对吧?

@eivindr在以空白表单开始然后以编程方式设置表单控件的值时,我也

@MarkPerryBV不,在使用 required 时,我仍然收到“检查后已更改”的信息。 使用模式验证器时相同。 但 minLength 等工作。

@ user414如果我删除 [ngFormControl] ... 输入中没有“必需”验证。

@MarkPerryBV我找到了一个对我

使用enableProdMode()并没有真正解决任何问题 - 它只是通过阻止它进行双重检查并通知您可能有问题来掩盖症状。

AerisG222 的修复似乎比使用setTimeout更好。 在使用验证器调用addControl()后,预期行为是否真的必须手动触发更改检测?

调用 addControl() 后出现同样的问题。 我认为 addControl() 应该调用 ChangeDetectionRef.detectChanges()。

@CaptainCodeman我知道 - 这是一个临时的解决方法。

当我使用ng2@beta17[email protected] ,区域时,这将不再在开发模式下失败。 [email protected] 我还是要调用 ChangeDetectionRef.detectChanges()

@eivindr你能更新你的 plnkr 到 beta17 修复吗?
我对这个失去了理智,我拒绝提交给enableProdMode()

@AerisG222你的方法对我

使用@AerisG222发布的方法是一种很好的使用方法吗? 是的,它确实有效,但是否有任何良心手动触发changeDetectionRef.detectChanges()

我在尝试进行动态表单验证然后重置表单时遇到了同样的问题。 这是我的笨蛋

重现步骤。 使用 Is Employee Checked 提交表单。 表单刷新后再次检查是员工。

我在提交函数中使用 changeDetectionRef.detectChanges()。

谢谢@AerisG222 :)

我不喜欢所有 setTimeout 解决方法。 我提出以下建议:

http://plnkr.co/edit/9umnTGsdFWhbaOBeftuZ?p=preview

使用ChangeDetectorRef.detectChanges并不总是一个好方法。 示例原因:

父组件在其ngAfterViewInit期间调用子组件的公共 API。 这个调用改变了孩子的绑定。 从父级调用detectChanges也可以解决这个问题,但这意味着父级必须知道何时调用detectChanges之后调用子级的 API。 您会注意到,当您想使用ChangeDetectionStrategy.OnPush时,在这种情况下更改绑定的 childs 方法中调用detectChanges不起作用。

我在通过ViewContainerRef.createComponent()动态添加子项然后更新子实例时遇到了这个错误。 我正在使用 RC2(并尽量不使用折旧的东西)。

最初工作正常(第一次渲染),但后来我添加了一个可以发出新数据的组件。 这个事件被父组件监听,它更新了它的数据数组,触发变化检测和新动态子组件的填充。 该事件由表单提交触发。 如果我按下提交按钮,一切都很好。 如果我按 Enter 键生成提交,则会产生错误。

我最终在动态组件的创建和更新之后立即添加了ChangeDetectorRef.detectChanges() 。 这不是一个大问题,但即使我刚刚向视图添加了一个新组件,系统也不会自动触发子项的更改检测,这似乎很奇怪。

@ aerisg222我是否需要在每次从父级更改子级属性后调用 this._changeDetectionRef.detectChanges()?

嗯,我刚刚遇到了这个问题。 我有两个使用相同数据对象的兄弟组件。 假设这两个对象都能够更新数据源。 另一个源实际上看到了对象的变化,但在检查后触发表达式发生了变化。 以前的值:'真'。 当前值:'假'

这发生在我从 RC1 升级到 RC4 后

同样在这里。 从 RC1 升级到 RC4 后又开始得到这个。

也许导致这种情况的更改应该作为更改日志中的重大更改进行传达......因为我的东西现在坏了。

我们需要等待决赛。
我停止使用子组件,因为更新外部属性很糟糕。
我以为区域是为了那个...

同样在这里。 通过 ViewComponentRef 加载动态组件在 RC2 和 RC4 中的行为似乎与在 RC1 中的行为不同

这是我的工作依赖项:
“依赖关系”:{
"@angular/common": "2.0.0-rc.1",
"@angular/compiler": "2.0.0-rc.1",
"@angular/core": "2.0.0-rc.1",
"@angular/http": "2.0.0-rc.1",
"@angular/platform-b​​rowser": "2.0.0-rc.1",
"@angular/platform-b​​rowser-dynamic": "2.0.0-rc.1",
"systemjs": "0.19.31",
"core-js": "^2.4.0",
“反射元数据”:“^0.1.3”,
"rxjs": "5.0.0-beta.6",
“zone.js”:“^0.6.12”
},
“开发依赖”:{
"打字稿": "^1.8.10",
"打字":"^1.0.4",
"同时": "^2.2.0",
“精简服务器”:“^2.2.2”
}

以下是产生上述错误的依赖项:
“依赖关系”:{
"@angular/common": "2.0.0-rc.4",
"@angular/compiler": "2.0.0-rc.4",
"@angular/core": "2.0.0-rc.4",
"@angular/http": "2.0.0-rc.4",
"@angular/platform-b​​rowser": "2.0.0-rc.4",
"@angular/platform-b​​rowser-dynamic": "2.0.0-rc.4",
"systemjs": "0.19.31",
"core-js": "^2.4.0",
“反射元数据”:“^0.1.3”,
"rxjs": "5.0.0-beta.6",
“zone.js”:“^0.6.12”
},
“开发依赖”:{
"打字稿": "^1.8.10",
"打字":"^1.0.4",
"同时": "^2.2.0",
“精简服务器”:“^2.2.2”
}

RC2 也不工作!

只是一个问题,但有没有办法获得有关错误的更多信息? 例如,当错误提示“表达式从‘真’变为‘假’!”时,最好知道“表达式”是什么。

从 RC1 升级到 RC4 后,我也遇到了完全相同的问题。 我发现这是由于我的组件中的ngOnInit函数。 如果我注释掉这个函数,错误信息就会消失,一切正常。 有什么想法吗?

关闭此问题,因为它已经过时并且已采取措施。 如果您仍然有问题,请确保创建一个新问题并使用所有必需的详细信息填写问题模板。 谢谢

但是人们说他们在新的rc中仍然遇到这个问题......

2016 年 7 月 13 日星期三 00:52,Victor Berchet通知@github.com
写道:

关闭 #6005 https://github.com/angular/angular/issues/6005。


你收到这个是因为你被提到了。
直接回复本邮件,在GitHub上查看
https://github.com/angular/angular/issues/6005#event -720794445,或者静音
线程
https://github.com/notifications/unsubscribe/AAgIEFEVZmE0M53XxNpzAYg9hJT_E_viks5qVAyGgaJpZM4G4J_s
.

是的,我刚刚升级到 RC4,除了配置新路由器之外没有对我的任何组件进行任何更改,并遇到了这个问题。 在 RC1 中一切都很好,似乎这仍然存在。

更多信息:

事实证明,由于async内部调用ngOnInit ,会发生此错误。 如果我注释掉异步调用代码或将那段代码移到构造函数中,则一切正常。

@ammar91
更改后调用:
changeDetectionRef.detectChanges()

@zigzag95是的,这确实是一种解决方法,但这样做感觉非常

就我而言,当我在app.component.ts订阅角度事件发射器以显示和隐藏进度条时,就会发生这种情况。 RC1 一切都很好,但在 RC4 中失败了。

这是再现, http: //plnkr.co/edit/jEtfUrQdc4sJBySj5kWN?
单击英雄链接并选择要编辑的英雄之一,您将看到错误。
请查看app.component.tshero.service.ts

@vicb ,感谢您的更新。 你能告诉我们采取了哪些措施吗? 你的意思是这个会在下一个版本中修复吗?

另一个对我有用的简单替代解决方案是将值返回为可观察值并将发射值延迟 10 毫秒。 在我的情况下,异常发生在微调器值上
<app-spinner *ngIf="spinner"></app-spinner>

  this.eventService.toggleSpinnerAnnounced$.subscribe(
      (show: boolean) => this.spinner = show
    );

这对我有用:

private spinner = new Subject<boolean>();
toggleSpinnerAnnounced$ = this.spinner.asObservable().delay(10); // This  delay() is important

announceSpinnerToggle(show: boolean) {
        this.spinner.next(show);
    }

这对您有用,因为您正在使用延迟并且它与 observable 无关。
使用 stettimeout 时,您将具有相同的行为。

@Jarvens请停止在

GitHub 问题用于错误报告和功能请求。
对于支持问题,请使用其他渠道,例如CONTRIBUTING - Got a Question or Problem?

您的评论没有提供足够的信息来诊断您的问题。

仅供参考:由于 chrome 扩展ng-inspector for AngularJS (angular 1 扩展替代 batarang),我收到此错误

这太疯狂了! 正如@KarlGustav-unimicro 所说,在阅读了整个线程后,在开发模式下解决问题的方法是禁用AngularJS 的 ng-inspector

话虽如此, ChangeDetectorRef.detectChanges()也对我有用,但感觉真的很糟糕。

@pkozlowski-开源,
我仍然不知道为什么当输入更改之一时会调用indicatorStyle()
你能给出更多解释吗?
如果我在 _progress-bar component_ 中实现ngDoCheck而没有在父组件中设置ChangeDetectionStrategy.OnPush
我发现ngDoCheck执行了两次,
我知道为什么第一个会被处决,
但我真的对第二个ngDoCheck感到困惑......

http://plnkr.co/edit/myX2Alx9jie2q0FDIME7?p=preview

@李天

我仍然不知道为什么当输入之一发生变化时会调用 indicatorStyle(),
你能给出更多解释吗?

由于indicatorStyle()绑定到视图中的[ngStyle] ,因此在更改检测器运行时将始终调用它。 无论输入是否实际更改,这都会发生。

我发现 ngDoCheck 执行了两次,
我知道为什么第一个会被处决,
但我真的对第二个 ngDoCheck 感到困惑......

这很可能是因为,正如本线程前面提到的,更改检测总是在开发模式下运行两次。 它这样做是为了验证我们没有在更改检测期间更改绑定的代码,这可能会导致生产中的困难错误。

setTimeout() 的变体

更新:CPU 在没有同伴高度的情况下 100% 疯狂上次比较:AFAIK 更新正在递归!

UPDATE2: Plunker 示例

我想要动态 <ul> 块的高度来控制我绑定到“companionHeight”的 <img> 的高度。

所以邪恶......但使用下列作品可观察到的。

这是正在监视高度的 <ul> 元素:

<ul class="title-list-ul" #listul (window:resize)="setCompanionHeight(listul)">

这是被控制的 <img> 元素:

<img class="title-list-companion-img" [src]="getCompanionImageURL()" [height]="companionHeight"/>

以下是交互的组件方法:

import { Component, OnInit, AfterViewChecked, ElementRef } from '@angular/core';  // redacted
import { Observable } from 'rxjs';

@Component({
  selector: 'resume-title-list',
  templateUrl: './title-list.component.html',
  styleUrls: ['./title-list.component.scss']
})
export class TitleListComponent implements OnInit, AfterViewChecked {

  private error: Object;
  private companionHeight: number = 0;
  private companionHeightLast: number = 0;

   // Lots of irrelevant stuff like ngInit redacted for simplicity.

  constructor(private elementRef: ElementRef) { }  // redacted, mine is more complicated.

  setCompanionHeight(listElement: any) {
    let clientRect = listElement.getBoundingClientRect();
    if (clientRect) {
      this.companionHeight = clientRect["height"];
    }
  }

  // window::resize only happens when the window is resized: This is an initialization 
  // function that sets the initial size of the image... Without it the image height is 
  // initially zero and the image is not shown. 
  // The height was not correct in ngAfterViewInit, so I used ngAfterViewChecked instead.

  ngAfterViewChecked() {
    let ularray = this.elementRef.nativeElement.getElementsByClassName("title-list-ul");
    if (ularray && ularray.length > 0) {
        let h = ularray[0].getBoundingClientRect().height;
        if (h && h != this.companionHeightLast) {
          this.companionHeightLast = h;
          Observable
          .of(h)
          .delay(10)
          .subscribe(h => this.companionHeight = h,
                     error => this.error = error);
        }
    }
  }
}

Angular 团队成员可能不会阅读对已解决问题的评论。
在这些频道之一中发布问题怎么样? CONTRIBUTING - Got a Question or Problem? 用Plunker来繁殖?

我有同样的问题:

<div *ngIf="isLoading">
   <app-some-viewchild></app-some-viewchild>
</div>

用 [hidden] 代替 *ngIf 解决问题。

我也遇到了这个问题。 最后,我发现默认的 inition 对象和 ngModle 对象是一样的。

@Component装饰器中设置changeDetection: ChangeDetectionStrategy.OnPush为我修复了它

@elvismercado谢谢,在 2.1.1 中解决了我的问题!

我有一个依赖父模板传递的值的子组件,当父值更改时,子模板会抛出此异常。 将您的建议添加到子组件会导致更改检测在将更改的值推送到子组件之前不会运行,从而避免检测过早运行我假设?

如果我有一个具有焦点的输入,然后将其从 DOM 中删除(ngIf 条件),则会出现错误“检查后表达式已更改”(仅当输入具有关联的表单组时)。
这是一个有问题的plunker:
https://plnkr.co/edit/dooRvC1gY1WEcaNdshoP?p=preview
谢谢!

那么这里的解决方案是什么?

更改 ngOnInit 中的任何输入值都会触发此操作。

@coli
只是包装成

setTimeout(()=>{
 ///your code here
}, 1);

当用户想要编辑现有实体时,我需要预加载子组件的组合。
我一直在努力解决这个问题几个小时,直到我设法使用上面的@drewlio帖子修复它。

注入 _changeDetectionRef:

    constructor(
        private _changeDetectionRef: ChangeDetectorRef) {
    }

然后在进行更改后立即调用detectchanges。
这对我有用。

    setSizeFromCtrl() {
        console.log("setSizeFromCtrl");
        if (this.sizeFromList && this.tradeProfile && this.tradeProfile.SizeFrom && this.sizeFromCtrl) {
            this.sizeFromCtrl.select(String(this.tradeProfile.SizeFrom));
            this._changeDetectionRef.detectChanges();
            console.log("setSizeFromCtrl DONE");
        }
    }

    setCentreTypeCtrl() {
        console.log("setCentreTypeCtrl");
        if (this.centreTypeList && this.tradeProfile && this.tradeProfile.centreTypeList && this.centreTypeCtrl) {

            for (let i of this.tradeProfile.centreTypeList) {
                this.centreTypeCtrl.select(i.value);
            }
            this._changeDetectionRef.detectChanges();
            console.log("centreTypeCtrl DONE");
        }
    }


请注意,我遇到了同样的问题,但有不同(相似?)的原因和不同的解决方案。

  • 我们的 Angular 应用程序 - https://github.com/GeoscienceAustralia/GNSS-Site-Manager具有在ngOnInit()跨多个组件嵌套和构建的模型反应形式。 其中一些表单在模板中折叠为ngIf
  • 数据也在ngOnInit()读入,并使用theModelForm.patchValue(data)
  • 当展开折叠的表单之一时,会出现此错误。 这与 DOM 没有构建的事实有关,因为表单是在ngOnInit()中创建的,直到它展开才触发。 取出patchValue()问题就解决了
  • 我们提出了两个解决方案
  • patchValue()运行this._changeDetectionRef.detectChanges(); 。 并将ngIf更改为[hidden]以便完全构建表单和 DOM。 这样做的缺点是创建完整表单和相关 DOM 时的时间和内存成本(这在许多应用程序中可能不是问题,但对我们来说是一个巨大的表单)。
  • 更好的解决方案是在组件中应用patchValue() (或setValue()如果数据和字段是 1:1),因此只有在ngOnInit()创建表单时才会发生.

我还没有提交这些更改(22/3/17)。

所以我不认为这个决定会拯救所有人。
但是在我看来,给定错误的发生伴随着类的新元素的创建。 然后将其添加到数组中,该数组通过 *ngFor 输出。
我通过向我的类添加一个构造函数解决了这个问题,它填充了在模板中显示默认值(零、空行)所需的字段

export class Task {
    task_id: number;
....
    constructor() {
        this.task_name = '';
        this.task_desc = '';
        this.task_percent = 0;
  addNewTask():void{
    let newTask = new Task();
    this.tasksList.push(newTask);
  }

task_name & task_desc & task_percent 我在模板中显示。 如果至少有一个列出的字段未在构造函数中初始化,我会收到类似的错误。

+1

AerisG222 解决方案工作

constructor(private _changeDetectionRef : ChangeDetectorRef) { }

ngAfterViewInit() : void {
    // your code

    this._changeDetectionRef.detectChanges();
}

this._changeDetectionRef.detectChanges() 有效,但在我的情况下它会导致其他问题。 我只是将代码包装在 setTimeout() 函数中,它解决了它。

@GuofuHuang这样做是为了避免不修复问题的一种方式,这有时会导致无限的变更检测周期

@Toxicable抱歉,可能我没说清楚,我没有使用 this._changeDetectionRef.detectChanges(); 相反,我将源代码包装在 setTimeout() 中。

@GuofuHuang如果您在setTimeout放入的任何内容都会导致 CD 并且您在生命周期挂钩上运行它,那么您可能会导致无限的 CD 周期。 解决方案不是在 CD 运行期间进行导致 CD 的更改

@Toxicable即使我将其设置为 0?

@GuofuHuang如果设置为 0 会导致 CD,则是

@Toxicable我只是找到这个链接, http: //stackoverflow.com/questions/779379/why-is-settimeoutfn-0-sometimes-useful ,它说 setTimeout 0 类似于 C 中的暂停/产量,我不不知道它是否会导致任何 CD,但现在它在我的工作中完美无缺。 如果没有这个解决方案,你有更好的解决这个问题的方法吗?

@GuofuHuang我不知道 pause/yield 在 C 中做了什么,但在 JS 中它会将回调放在事件队列的末尾。 我的解决方案如上:解决方案是在 CD 运行期间不进行导致 CD 的更改

settimeout 是解决方案
您不需要指定 ms 参数,我将等待一滴

@zigzag95 @GuofuHuang查看此示例了解为什么setTimeout不是解决方案
https://plnkr.co/edit/dv8K9EvVQLG59Gxsl3oI?p=preview

@Toxicable谢谢,但我的情况与这个大不相同,我没有看到任何无限循环。 但这绝对是一个很好的例子,可以帮助我更多地了解 angular。

@GuofuHuang实际上,在您的情况下不会发生这种情况,因为您是在ngAfterViewInit ,它只被调用一次。 我的观点是,使用这种方法可能会导致无限循环,因此使用起来很危险。 这就是 plunkr 显示的内容。

@moutono你的技巧帮助了我! 非常感谢😄

由于问题与触发更改检测有关,因此解决承诺也可能有效,因为 ngZone 猴子补丁承诺也是如此。 这个对我有用:

ngAfterViewInit() {
  Promise.resolve().then(() => your_code_here);
}

是的 setTimeout 有效,但我不喜欢那个修复程序,在我的情况下,这是因为我正在更改 EventEmitter 上的值
解决方案是改变
private generalSubscriber: EventEmitter<any> = new EventEmitter<any>();

private generalSubscriber: EventEmitter<any> = new EventEmitter<any>(true);

很奇怪吧?
那么这真的意味着,事件发射器是异步的
https://angular.io/docs/ts/latest/api/core/index/EventEmitter-class.html

有什么解决办法吗? 为什么关门了?

我每次都必须清除缓存

在我的情况下,我正在替换图像加载时的图像存根,不得不将其放入setTimeout

@intergleam @wasa4587 @caroso1222 @tandu @AerisG222 @drew-moore @robwormald @svi3c @pkozlowski-opensource @pcroc @zoechi @stefankoru @bennadel @bryanforbes @Necroskillz @drewlio @ElsewhereGames @ElsewhereGames @zoechie

它在开发时面临错误。 在生产服务器上工作正常。

@smartHasmukh这是一个仅限开发模式的错误,因此不足为奇。
你仍然应该修复它。

如果发生此错误,那么您很可能会破坏一个方向的数据流。 这可能类似于更改兄弟组件中一个组件的数据,更改子组件生命周期钩子中父组件的数据可能还有更多其他事情。
抛出的这个异常不是错误,而是帮助开发人员了解单向数据流合同以某种方式被破坏。 这在生产中起作用的事实是偶然的,您应该检查在父子通信中如何更改数据,并在开发模式下修复它。

@smartHasmukh我曾经也觉得这很不舒服。 例如,我挂钩导航事件并进一步向主要组件发出一个事件以显示加载器。 然后在每个组件中,在我从服务器接收数据后(因此,在订阅中)隐藏该加载程序。 没关系。 但是,在一个组件中,我只从“静态”源获得了一些数据,在那里无法观察到,所以我只是尝试使用 ngOnInit 告诉主组件隐藏加载器。 然后我得到了错误,当然。

尽管我的情况应该是一件简单的事情,但这不是正确的做法。 对于那个组件,我必须首先决定不显示加载器(如果我有更多这样的组件,这可能会变得更加棘手)......或者,还处理从我的数据服务中隐藏加载器, 而不是在组件级别。 或者,一个丑陋的解决方法是将一个特定案例包装在 setTimeout() 中。 因为最初的方法不正确。

TL/DR:
我确定您的场景也必须有适当的方法,除了 _setTimeout()_ 丑陋的修复(我当时_可悲地_选择了它,但话又说回来,这是一个非常简单的场景,我知道什么是在那里继续 - 但是,我可能不得不在将来的某个时候更改它)。
您可以针对您的具体情况尝试StackOverflow ,提供更多详细信息。 必须有办法。

当然由于@MrCroft @tytskyi,我将做到这一点@zoechi先生,我知道这是毫不奇怪。 但我只是让你知道。 没有其他的

@tytskyi

这在生产中起作用的事实是偶然的,您应该查看

不,在大多数情况下,我对这个错误的回应是“是的,我知道,而且是有意为之”。 我的应用程序中的许多组件在 init 之后和在绑定变量中接收到新值之后都在加载一些东西。 这个问题只是 Angular 的“已知丑陋”,没人关心。

@e-oz 如果您正在以某种方式影响任何生命周期挂钩中的父级或兄弟级状态,那么这将破坏单向数据流。 您应该在应用程序状态稳定时进行此类更改,而不是在更改检测过程中。 这就是为什么有 microtask、setTimeout 0 等技巧的原因。

@tytskyi对您的“应该”冷静下来,我在稳定后进行更改 - 如果仔细阅读很明显 - 加载额外数据的 http 请求显然比初始稳定需要更多时间。 你不会让我相信 Angular 的这种行为是正确的,甚至不要尝试。

@e-oz 这与 HTTP 请求有什么关系。 HTTP 请求响应会导致更改检测,并且您不会收到“表达式已更改...”错误。
你能提供更多信息吗?

@zoechi没有理由在上面浪费时间,没有人会改变任何东西,任务已关闭。

@e-oz 问题在一年前关闭,当时 Angular 团队有完全不同的优先级(发布一个版本),这个问题当时还不是障碍。
如果你认为你有一个有效的案例,你应该创建一个新问题。

@zoechi我认为你不会决定我应该做什么。 我可以创建一个新问题,但不是必须的。 出于尊重,我不喜欢那种“你应该”的语气。

@e-oz 我明白了,您的评论只是为了发泄您的挫败感,您对获得解决方案并不感兴趣。 请不要再用你无用的评论在这里浪费别人的时间了。

如果您正在调用 http,请订阅您的组件,只需添加..
Promise.resolve().then(() => your_code_here);
或者
myObservable.delay(10).subscribe(...)

两种解决方案之一将解决您的问题

@vicb停止关闭未修复的问题。 你为什么做这个 ?

至少建议一些正确的修复,但关闭它是不可接受的

@diegogarciaa ......从您的角度来看,究竟有什么没有修复或错误? 对话^^^太长,很难找到真正的核心。

@mlc-mlapis 恕我直言,我不是您的老师,如果您无法阅读或理解问题,请停止打扰。 我花了很长时间阅读所有内容,我推荐相同的内容。

angular 团队(不是所有团队,但谁在这里解决了这个问题)的态度似乎很有进取心:不阻塞(我的薪水),不尽快修复它。

@diegogarciaa ......非常好。 继续像上面一样,你会在任何地方受到欢迎。

@mlc-mlapis 我不想在人们无法阅读的地方受到欢迎,也不想在不应该的地方胡说八道。

@diegogarciaa ...我只是认为您个人有问题,现在看来并非如此。 现实情况也是, Expression has changed after it was checked ...的大部分问题不是错误,而是对 CD 工作原理的理解不正确。

@mlc-mlapis 好的...让我们理解

http://plnkr.co/edit/nm8OkrpZCIp4cvA6TbpO?p=preview

在这个 plunk 中,我可以理解在调用 ngAfterViewInit 时绑定并检查了变量,因此当它接收到新值并且不触发新的检查轮时,会抛出异常

但是例如,如果我确实有一个视图容器引用...并且我使用例如:

this.service.GetData().subscribe(
响应 => this.component.instance.dataModel = 响应
);

考虑到 dataModel 在我的 html 中被用作 {{ dataModel.Field }}

this.component 是在某些事件中加载的动态组件,如何避免错误? 在更改检测的生命周期挂钩之前,将数据传递给我的组件的正确方法是什么

@diegogarciaa ...是的,这是一个典型的例子,其中异步和同步操作混合在一起并在一张 CD 中相遇,并在开发模式下进行确认。 我不会重复https://hackernoon.com/everything-you-need-to-know-about-the-expressionchangedafterithasbeencheckederror-error-e3fd9ce7dbb4帖子中的解释,所以也许尝试先阅读它。

并查看https://hackernoon.com/here-is-how-to-get-viewcontainerref-before-viewchild-query-is-evaluated-f649e51315fb。

所以文章基本上说我必须手动调用一轮变化检测......

老实说,我对目前的状态感到非常沮丧……即使是 angular 中最起码的事情也是如此无聊和累人……我什至无法呼吸。 去你的

@diegogarciaa ...不,这只是一个辅助解决方案。 您也可以使用promise.thensetTimeout ... 但在这种情况下推荐的解决方案可能是共享服务,因此您不会直接更新动态组件的属性跨 CD 周期及其确认。

或者只是在您拥有数据的那一刻创建动态组件的实例。 因此,您的订阅将调用动态组件实例的创建。

但重要的是 -> 发生错误的主要逻辑 -> 因为单向更改应始终从上到下流过整个树,并且在一个 CD 周期后,确认单向原则得到遵守的状态。

这个“操你”不是针对个人的……只是发泄我的沮丧。

我们使用指令模式来处理我们的组件。 我们构建了一个将组件加载到组件视图引用中的帮助器,正如我们所理解的,加载组件时数据必须可用

protected loadPartialView(viewName: string, target: ViewContainerRef) : ComponentRef<any> {
        const factories = Array.from(this.resolver['_factories'].keys());
        const factoryClass = <Type<any>>factories.find((x: any) => x.name === viewName);
        const factory = this.resolver.resolveComponentFactory(factoryClass);
        return target.createComponent(factory);
}

在上面的例子中:我想做这样的事情:

return target.createComponent(factory, dataSource);

我的数据源在构造函数时可用

像 redux 一样,它使用对象来处理通过组件传递的数据源,我们正在考虑实现一个可注入的组件来为我们处理数据,因此我们可以在生命周期钩子之前获取它。

迭戈加西亚

我不认为数据必须在之前可用......动态组件仍然是具有@Input()@Output()但问题是您何时更改数据。

...数据必须在加载组件时可用 ...

我想您还需要使用延迟加载模块中的组件,因此我直接在模块中使用映射,以便可以仅使用字符串名称访问组件。

export default class LazyLoadedModule {
    cmps: Map<{}, {}> = new Map();
    constructor() {
        this.cmps.set('first-component', FirstComponent);
        ...
    }
}

return target.createComponent(factory, dataSource);应该是什么意思? 因为实际的 API 是createComponent(componentFactory, index, injector, projectableNodes, ngModule)

也许新的@angular/cdk 模块对你来说很有趣。 在这里介绍一下: https ://medium.com/@caroso1222/a -first-look-into-the-angular-cdk-67e68807ed9b。

对于它的价值以及它是否可以帮助任何人,而不是使用setTimeout并且使用ChangeDetectorRef.detectChanges()对我不起作用,我最终使用NgZone.onStable并执行我的代码通过在EventEmitter上订阅一次 ...

constructor(
  private zone: NgZone,
) { }

ngAfterViewInit() {
  this.zone.onStable.first().subscribe(() => {
    // your code here
  });
}

我不完全意识到这样做的后果,但它会比使用setTimeout更糟糕吗? 如果有人对此事有任何评论,将非常受欢迎!

@AerisG222解决方案对我

constructor(private _changeDetectionRef : ChangeDetectorRef) { }

ngAfterViewChecked() : void {
    // your code
    this._changeDetectionRef.detectChanges();
}

这是 html 组件:

{{ lastData.note[i].date | 日期:'dd.MM.yyyy'}} | {{ lastData.note[i].date | 日期:'HH:mm' }}#BFL15_200817 | {{ lastData.status.name }} by {{lastData.note[i].admin}}

如果数组中的数据不止一个,我会收到错误消息,有什么解决方案吗?

这当然是一个复杂的问题,但对于某些情况(尤其是当您修改 ContentChild 上的属性时)可以通过将逻辑移动到 ngAfterContentInit() 而不是 ngAfterViewInit() 或 ngOnInit() 来解决

令人惊讶的是,简单的事情会在角度上引起如此多的混乱

不用担心,其他人也注意到了😕
image

@jakeNiemiec蹩脚的工作,巨魔,没有人再搜索 Angularjs
https://trends.google.com/trends/explore?date=all&q=Angular%20tutorial ,React%20tutorial
image

是的,我希望更多的人出于绝望需要谷歌“角度教程”,因此对“混乱”的初步评论。

但别担心,我们可以检查 npm 以获取确切的使用统计信息: http: //www.npmtrends.com/react-vs-@angular/cli -vs-vue-vs-@angular/core
image
image

只需比较问题的数量。 我真的很想看到 angular 变得更好,但我怀疑 Vue.js 会在遥远的未来通过它。 编写了所有 3 个程序后,我强烈向 Angular 开发人员推荐 Vue。 这就像没有样板的 angular。

好的答案被接受,我对这些统计数据非常满意。
我想在印度使用 PHP 的人比在全世界使用 Java 的人还多。 将PHP用于大型企业应用程序将是一个白痴,因此您有时应该使用自己的大脑,生活会更好

@jakeNiemiec ... 50% 的问题被错误地放置在那里......它们实际上是支持/知识问题,根本不应该在那里。 有趣的是,大多数必需的主题都在官方文档中得到了很好的记录 + 答案基于 JavaScript 的知识。 但是你知道,人们对文档的阅读不够深入。

@HostBinding可能是此错误的一个令人困惑的来源。

考虑一个组件上的简单绑定,例如@HostBinding('style.background')

该属性由*parent *组件的更改检测检查,就好像它“拥有”它一样。 从我们的角度来看,我们希望将其视为“拥有”该属性的子组件。

因此,在这种情况下,似乎应该有一种方法可以让组件排除用于检查的属性? 或者比这更复杂?

我必须让我的父组件运行detectChanges (或在子组件中添加一个额外的 div)以避免在子组件只是改变自己的背景颜色的情况下出现错误(并且永远不会在其他任何地方这样做) .

更完整的描述: https :

您只需要做一些事情,任何事情,在该方法期间触发另一轮更改检测 - 发出事件,无论如何。 将它包装在超时中(队列闪回到 ng1 :-P)是一种可行的方法,但在 ng2 中对我来说感觉非常混乱。

在角度 4 中,工作 4 me <3

由于不活动,此问题已自动锁定。
如果您遇到类似或相关的问题,请提交新问题。

阅读有关我们的自动对话锁定政策的更多信息。

_此操作已由机器人自动执行。_

此页面是否有帮助?
0 / 5 - 0 等级