@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}}}中
启动视图时,我正在做的只是更新一个简单的绑定?
#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 楼
您还可以使用rxjsObservable.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!
评论
有关ExpressionExpressdAfterItHaHasBeenCheckedError错误,您需要了解的所有文章都详细介绍了该行为。在使用detectChanges()stackoverflow.com/questions/39787038/…时,请考虑修改您的ChangeDetectionStrategy。
只需考虑存在一个输入控件,您就可以在一种方法中向其中填充数据,并且在同一方法中为其分配一些值。肯定会使编译器与new / previous值混淆。因此,绑定和填充应该以不同的方法进行。