为什么这个简单的插头中的组件

@Component({
  selector: 'my-app',
  template: `<div>I'm {{message}} </div>`,
})
export class App {
  message:string = 'loading :(';

  ngAfterViewInit() {
    this.updateMessage();
  }

  updateMessage(){
    this.message = 'all done loading :)'
  }
}


投掷:


例外:表达式'I'm {{message}检查后,App @ 0:5'中的}已更改。上一个值:'我正在加载:('。当前值:'我已经完成加载:)'位于App @ 0:5中的[I'm {{message}}}中


启动视图时,我正在做的只是更新一个简单的绑定?

评论

有关ExpressionExpressdAfterItHaHasBeenCheckedError错误,您需要了解的所有文章都详细介绍了该行为。

在使用detectChanges()stackoverflow.com/questions/39787038/…时,请考虑修改您的ChangeDetectionStrategy。
只需考虑存在一个输入控件,您就可以在一种方法中向其中填充数据,并且在同一方法中为其分配一些值。肯定会使编译器与new / previous值混淆。因此,绑定和填充应该以不同的方法进行。

#1 楼

如drewmoore所述,这种情况下的正确解决方案是手动触发当前组件的更改检测。这是通过使用detectChanges()对象(从ChangeDetectorRef导入)的angular2/core方法或其markForCheck()方法完成的,该方法还可以更新任何父组件。相关示例:

import { Component, ChangeDetectorRef, AfterViewInit } from 'angular2/core'

@Component({
  selector: 'my-app',
  template: `<div>I'm {{message}} </div>`,
})
export class App implements AfterViewInit {
  message: string = 'loading :(';

  constructor(private cdr: ChangeDetectorRef) {}

  ngAfterViewInit() {
    this.message = 'all done loading :)'
    this.cdr.detectChanges();
  }

}


这里还有Plunker演示ngOnInit,setTimeout和enableProdMode以防万一。

评论


就我而言,我正在打开一个模态。打开模态后,它显示消息“表达式___已检查后已更改”,因此我的解决方案添加了this.cdr.detectChanges();。打开我的模态后。谢谢!

–乔治·卡萨列戈(Jorge Casariego)
16 Sep 8'在15:38

您对cdr属性的声明在哪里?我希望在消息声明下看到像cdr这样的行:any或类似的东西。只是担心我错过了什么?

– CodeCabbie
17 Mar 9 '17 at 10:54



@CodeCabbie它在构造函数参数中。

–傻
17年3月31日在23:08

此分辨率解决了我的问题!非常感谢!非常清晰和简单的方法。

– lwozniak
17-10-31在7:26

这对我有所帮助-进行了一些修改。我必须设置通过ngFor循环生成的li元素的样式。我需要在单击“排序”后根据innerText更改列表项中跨度的颜色,这会更新一个用作排序管道参数的布尔值(排序后的结果是数据的副本,因此样式无法获取仅使用ngStyle更新)。 -我没有使用'AfterViewInit',而是使用了'AfterViewChecked'-我还确保导入并实现AfterViewChecked。注意:将管道设置为“ pure:false”无效,我必须添加此额外步骤(:

– Chloe Corrigan
18年7月20日在14:25



#2 楼

首先,请注意,只有在以开发人员模式运行应用程序时才会引发此异常(默认情况是beta-0起是这种情况):如果在引导应用程序时调用enableProdMode(),则不会引发该异常(请参阅更新的插件)。

其次,不要这样做,因为有充分的理由抛出该异常:简而言之,在开发人员模式下,每轮变更检测之后都会立即进行第二轮,以验证没有绑定自第一个结束以来已更改,因为这表明更改是由更改检测本身引起的。

在您的子弹中,绑定{{message}}通过调用setMessage()进行了更改,这发生在ngAfterViewInit中挂钩,这是初始更改检测转弯的一部分。不过,这本身并不成问题-问题在于setMessage()更改了绑定,但不会触发新一轮的更改检测,这意味着只有在以后的其他地方进行一轮更改检测之前,才会检测到此更改。

要点:更改绑定的任何内容都需要在更改时触发一轮更改检测。

更新以响应所有请求,以获取有关如何执行此操作的示例:@Tycho的解决方案有效,@ MarkRajcok答案中指出的三种方法也是如此。但坦率地说,他们都对我感到丑陋和不对劲,就像我们习惯于在ng1中依靠的那种骇客一样。

可以肯定的是,在某些情况下,有时适合使用这些技巧,但如果您偶尔使用这些技巧,则表明您在与框架斗争,而不是在与之搏斗。完全接受其反应性。

恕我直言,一种更惯用的“ Angular2方式”可以解决以下问题:(plunk)

@Component({
  selector: 'my-app',
  template: `<div>I'm {{message | async}} </div>`
})
export class App {
  message:Subject<string> = new BehaviorSubject('loading :(');

  ngAfterViewInit() {
    this.message.next('all done loading :)')
  }
}


评论


为什么setMessage()不触发新一轮的变更检测?我认为当您在UI中更改某些值时,Angular 2会自动触发更改检测。

–丹尼·利宾(Danny Libin)
2015年12月20日20:11在

@drewmoore“更改绑定的任何内容都需要在更改时触发一轮更改检测”。怎么样?这是一个好习惯吗?难道不应该一次完成所有事情吗?

–Daniel Birowsky Popeski
15/12/25在12:04

@Tycho,的确如此。自从我写了该评论以来,我已经回答了另一个问题,我在其中描述了运行更改检测的3种方法,其中包括detectChanges()。

– Mark Rajcok
16年2月6日在16:51

只是要注意,在当前问题正文中,被调用的方法名为updateMessage,而不是setMessage。

–superjos
16年8月22日在9:53

@Daynil,我有相同的感觉,直到我阅读了在以下问题下发表评论的博客:blog.angularindepth.com/…它说明了为什么需要手动完成此操作。在这种情况下,角度变化检测具有生命周期。如果在这些生命周期之间更改了某个值,则需要运行强制更改检测(或settimeout-在下一个事件循环中执行,再次触发更改检测)。

–马赫什
17年11月16日在2:30

#3 楼

ngAfterViewChecked()为我工作:

import { Component, ChangeDetectorRef } from '@angular/core'; //import ChangeDetectorRef

constructor(private cdr: ChangeDetectorRef) { }
ngAfterViewChecked(){
   //your code to update the model
   this.cdr.detectChanges();
}


评论


正如提请注意的那样,角度的变化检测周期为两个阶段,必须在修改儿童视图后检测变化,我觉得最好的方法是使用角度自身提供的生命周期挂钩,并手动请求角度检测更改并将其绑定。我个人认为这似乎是一个适当的答案。

–Joey587
19-10-3在10:40

这项工作对我来说,是为了将层次结构动态组件一起加载到内部。

– Mehdi Daustany
3月22日,0:01

为我工作:)

–Naveen Kumar V
8月21日12:22

#4 楼

我通过从角铁芯添加ChangeDetectionStrategy来解决此问题。

import {  Component, ChangeDetectionStrategy } from '@angular/core';
@Component({
  changeDetection: ChangeDetectionStrategy.OnPush,
  selector: 'page1',
  templateUrl: 'page1.html',
})


评论


这对我有用。我想知道这和使用ChangeDetectorRef有什么区别

–阿里
17年8月31日在4:56

嗯...然后更改检测器的模式将初始设置为CheckOnce(文档)

– Alex Klaus
17年12月8日在4:29

是的,错误/警告消失了。但是它比以前花费了很多时间,例如5-7秒的差异,这是巨大的。

–卡皮尔·拉格万希(Kapil Raghuwanshi)
19年6月28日在11:36

@KapilRaghuwanshi运行this.cdr.detectChanges();无论您要加载什么。因为可能是因为变更检测未触发

– harshal木匠
19/12/11在23:19

#5 楼

您不能使用ngOnInit,因为您只是更改了成员变量message吗?

如果要访问对子组件@ViewChild(ChildComponent)的引用,则确实需要使用ngAfterViewInit来等待它。

一个肮脏的解决方法是在下一个事件循环中调用updateMessage(),例如setTimeout。

ngAfterViewInit() {
  setTimeout(() => {
    this.updateMessage();
  }, 1);
}


评论


将我的代码更改为ngOnInit方法对我有用。

–法利恩
16年1月27日,11:05

#6 楼

为此,我已经尝试了以上答案,但在最新版本的Angular(6或更高版本)中,许多功能都不起作用

我正在使用Material控件,该控件需要在首次绑定后进行更改。

    export class AbcClass implements OnInit, AfterContentChecked{
        constructor(private ref: ChangeDetectorRef) {}
        ngOnInit(){
            // your tasks
        }
        ngAfterContentChecked() {
            this.ref.detectChanges();
        }
    }


添加我的答案,这样可以帮助解决某些特定问题。

评论


这实际上适合我的情况,但是您有一个错字,在实现AfterContentChecked之后,您应该调用ngAfterContentChecked,而不是ngAfterViewInit。

– Tomas Lukac
19年11月29日在16:15

我目前正在使用8.2.0版本:)

– Tomas Lukac
19/12/2在7:59

如果以上任何一项都不起作用,我建议此答案。

–阿米尔·乔巴尼(Amir Choubani)
3月13日8:16



每次重新计算或重新检查DOM时,都会调用afterContentChecked。从Angular版本9启发

– Imran Faruqi
4月29日下午2:02

是angular.io/api/core/AfterContentChecked你是change事件后的默认方法

– MarmiK
4月30日13:38



#7 楼

有关ExpressionChangedAfterItHasBeenCheckedError错误的所有信息,您需要了解的详细信息解释了该行为。

设置的问题是,ngAfterViewInit生命周期挂钩在更改检测处理的DOM更新之后执行。并且您正在有效地更改此挂钩中模板中使用的属性,这意味着需要重新渲染DOM:

  ngAfterViewInit() {
    this.message = 'all done loading :)'; // needs to be rendered the DOM
  }


,这将需要另一个更改检测周期,而Angular设计仅运行一个摘要周期。

您基本上有两种解决方法:


使用setTimeout异步更新属性,Promise.then或模板中引用的异步可观察对象
在DOM更新之前在钩子中执行属性更新-ngOnInit,ngDoCheck,ngAfterContentInit,ngAfterContentChecked。


评论


阅读您的文章:blog.angularindepth.com/…,将很快阅读另一篇blog.angularindepth.com/…。仍然不知道该问题的解决方案。你能告诉我如果我添加ngDoCheck或ngAfterContentChecked生命周期挂钩并添加此this.cdr.markForCheck();会发生什么情况? (其中的ChangeDetectorRef为CDR)。这是在生命周期挂钩并随后完成检查之后检查更改的正确方法。

–永远的学习者
19年4月1日在10:10

阅读您的文章并承诺,然后解决了我的问题。顺便说一句,这是当我注释掉enableProdMode()时发生的; ,我的意思是调试时间。 NG6,虽然没有发生,但是创建一个微任务很有意义。

– DavutGürbüz
8月12日15:39

#8 楼

我从AfterViewInit切换到AfterContentChecked,它为我工作。
这是过程


在您的构造函数中添加依赖项:
constructor (private cdr: ChangeDetectorRef) {}


,并在此处使用已实现的方法代码调用登录名:
 ngAfterContentChecked() {
     this.cdr.detectChanges();
  // call or add here your code
 }




评论


是的,这也为我工作。我正在使用AfterViewInit。

– abrsh
8月13日14:51

这有效,我想这应该是正确的答案。

–西拉姆巴拉桑(Silambarasan R.D)
9月17日下午6:05

对我来说,使用ngAfterContentChecked在性能方面似乎并不明智。在我的小样中,即使在angular完成视图初始化后滚动时,代码也会多次执行。对性能有任何想法吗?

– Smamatti
9月21日上午9:31

#9 楼

之所以会出现此错误,是因为初始化后会立即更新现有值。因此,如果您在DOM中呈现现有值后更新新值,则它将正常工作。就像本文中提到的Angular Debugging“检查表达式后更改了它”

例如,您可以使用

ngOnInit() {
    setTimeout(() => {
      //code for your new value.
    });


}



ngAfterViewInit() {
  this.paginator.page
      .pipe(
          startWith(null),
          delay(0),
          tap(() => this.dataSource.loadLessons(...))
      ).subscribe();
}


如你所见,我有setTimeout方法中未提及时间。由于它是浏览器提供的API,而不是JavaScript API,因此它将在浏览器堆栈中单独运行,并等待直到调用堆栈项完成。

Philip Roberts在YouTube视频之一中解释了浏览器API的调用方式(事件循环是什么?)。

#10 楼

您只需要在正确的生命周期挂钩中更新消息即可,在这种情况下,请使用ngAfterContentChecked而不是ngAfterViewInit,因为在ngAfterViewInit中,变量消息的检查已开始但尚未结束。

请参阅:
https://angular.io/docs/ts/latest/guide/lifecycle-hooks.html#!#afterview

所以代码只是:

import { Component } from 'angular2/core'

@Component({
  selector: 'my-app',
  template: `<div>I'm {{message}} </div>`,
})
export class App {
  message: string = 'loading :(';

  ngAfterContentChecked() {
     this.message = 'all done loading :)'
  }      
}


请参阅Plunker上的工作演示。

评论


我使用的是@ViewChildren()基于长度的计数器的组合,该计数器由Observable填充。这是唯一对我有用的解决方案!

–麦桑福德
17年3月13日在20:02

上面的ngAfterContentChecked和ChangeDetectorRef的组合为我工作。在ngAfterContentChecked上调用-this.cdr.detectChanges();

– Kunal Dethe
18 Mar 3 '18 at 12:09

#11 楼

您还可以在ngOnInt()-Method中放置对updateMessage()的调用,至少对我有用。

ngOnInit() {
    this.updateMessage();
}


在RC1中,这不会触发异常

#12 楼

您还可以使用rxjs Observable.timer函数创建计时器,然后更新订阅中的消息:


Observable.timer(1).subscribe(()=> this.updateMessage());


#13 楼

引发错误是因为调用ngAfterViewInit()时您的代码已更新。意味着ngAfterViewInit发生时,您的初始值已更改。如果在ngAfterContentInit()中调用它,则不会引发错误。

ngAfterContentInit() {
    this.updateMessage();
}


#14 楼

就我而言,这是通过p-radioButton发生的。问题是我在像这样的formControlName属性旁边使用了name属性(不需要):
<p-radioButton formControlName="isApplicant" name="isapplicant" value="T" label="Yes"></p-radioButton>
<p-radioButton formControlName="isApplicant" name="isapplicant" value="T" label="No"></p-radioButton>

我也有绑定到isApplicant表单控件的初始值“ T”,就像这样:
isApplicant: ["T"]

我通过删除单选按钮中的名称属性解决了这个问题。
由于两个单选按钮具有相同的值(T),在我的情况下这是错误的将其中一个更改为另一个值(例如F)也解决了该问题。

#15 楼

我没有评论@Biranchi的帖子,因为我没有足够的声誉,但是它为我解决了这个问题。

需要注意的一件事!
如果在组件上添加changeDetection:ChangeDetectionStrategy.OnPush无效,其子组件(哑组件)也尝试将其添加到父组件。

这修复了该错误,但我想知道它的副作用是什么。

#16 楼

使用数据表时出现类似错误。当您在另一个* ngFor数据表中使用* ngFor时,会发生此错误,因为它拦截了角度更改周期。因此,而不是在数据表内部使用数据表,而要使用一个常规表或将mf.data替换为数组名称。效果很好。

#17 楼

我认为一种最简单的解决方案如下:


实现一种通过函数或设置器将值分配给某个变量的实现。
在该类中创建一个类变量(static working: boolean)该函数存在的类,并且每次您调用该函数时,只需将其设为任意即可。在该函数中,如果working的值为true,则无需执行任何操作即可立即返回。否则,执行所需的任务。完成任务分配后,请确保在任务完成后(即在代码行的末尾或在subscribe方法中)将此变量更改为false!