为什么这个组件在这个简单的砰砰声中
@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}} in App@0:5'在被检查后发生了变化。之前的值:'I'm loading:('。当前值:'I'm all done loading:)' in [I'm {{message}} in App@0:5]
当我所做的一切都是更新一个简单的绑定时,我的视图被启动?
在我的例子中,它发生在p-radioButton上。问题是,我使用的名称属性(这是不需要的)旁边的formControlName属性,就像这样:
<p-radioButton formControlName="isApplicant" name="isapplicant" value="T" label="Yes"></p-radioButton>
<p-radioButton formControlName="isApplicant" name="isapplicant" value="T" label="No"></p-radioButton>
我还将初始值“T”绑定到is申请人窗体控件,如下所示:
isApplicant: ["T"]
我通过删除单选按钮中的name属性来解决这个问题。
此外,因为2个单选按钮具有相同的值(T),这在我的情况下是错误的,简单地将其中一个值更改为另一个值(例如F)也解决了这个问题。
关于ExpressionChangedAfterItHasBeenCheckedError错误的一切你需要知道的文章详细解释了这种行为。
你设置的问题是ngAfterViewInit生命周期钩子是在变更检测处理DOM更新后执行的。你在这个钩子中有效地改变了模板中使用的属性,这意味着DOM需要重新渲染:
ngAfterViewInit() {
this.message = 'all done loading :)'; // needs to be rendered the DOM
}
这将需要另一个变更检测周期,而Angular在设计上只运行一个摘要周期。
你基本上有两种解决方法:
使用setTimeout, Promise异步更新属性。然后在模板中引用异步可观察对象
在DOM更新之前执行钩子中的属性更新- ngOnInit, ngDoCheck, ngAfterContentInit, ngAfterContentChecked。
不错的答案。然而,在我看来,当我使用:
ChangeDetectorRef和AfterViewInit,
Angular会进入一些额外的渲染周期,如果我的HTML代码设计得不是很小心,或者需要在TS代码中调用几个依赖于刷新的函数,我就会得到额外的视图渲染调用,因此会有额外的处理。
这里有一个我喜欢使用的解决方案,因为我不需要担心任何这些,它在编程上非常简单,并且不需要我或系统太多额外的东西。每当Angular给我带来“Expression has changed after it was checked”这种臭名昭著的错误时,我使用它都没有问题。
我有这个小的public/exported函数,它只是通过一个零延迟承诺传递我的值。这样做的目的是迫使JavaScript/JS进入另一个后台周期,从而将值更新分离到下一个处理周期,并-防止错误。(请注意,JS的周期与Angular HTML的视图呈现周期不同,处理强度更低)。
export async function delayValue(v: any, timeOutMs: number = 0): Promise<any> {
return new Promise((resolve) => {
setTimeout(() => {
resolve(v);
}, timeOutMs);
});
}
现在,当我需要防止错误时,我简单地做:
this.myClassValue = await delayValue(newValue);
这只是一行代码。由于timeOutMs = 0的值,实际上没有明显的延迟。
下面是一个典型的场景:
myObservable$.subscribe(newValue = {
... // WHEN NEW VALUE ARRIVES FROM NOTIFIER(S)
this.handleSubscribedValues(newValue);
...
});
// THIS MAY GIVE YOU THE ERROR !
private handleSubscribedValues(newValue) {
this.myClassValue = newValue;
}
// SO, USE THIS INSTEAD TO AVOID THE ERROR
private async handleSubscribedValues(newValue) {
this.myClassValue = await delayValue(newValue);
}
你也可以使用delayValue()函数和一些延迟/超时值,如果你需要等待一些事情发生,例如给用户几秒钟。
希望这对你们中的一些人有用。
首先,请注意,这个异常只会在你以开发模式运行应用程序时被抛出(这是beta-0的默认情况):如果你在引导应用程序时调用enableProdMode(),它不会被抛出(参见updated plunk)。
其次,不要这样做,因为抛出这个异常是有充分理由的:简而言之,当处于开发模式时,每一轮更改检测之后都会立即进行第二轮,以验证自第一轮结束以来没有绑定发生更改,因为这将表明更改是由更改检测本身引起的。
在你的plunk中,绑定{{message}}是通过调用setMessage()来改变的,这发生在ngAfterViewInit钩子中,它作为初始变更检测回合的一部分发生。不过这本身并没有问题——问题是setMessage()改变了绑定,但不会触发新一轮的变更检测,这意味着直到在其他地方触发了未来的变更检测,才会检测到此更改。
结论:任何更改绑定的操作都需要触发一轮更改检测。
更新以回应所有关于如何做到这一点的例子的请求:@Tycho的解决方案是有效的,就像回答@MarkRajcok指出的三种方法一样。但坦率地说,我觉得他们都很丑陋,很错误,就像我们在ng1中习惯依赖的那种黑客一样。
可以肯定的是,在偶尔的情况下,这些hack是合适的,但如果您只是在非常偶然的基础上使用它们,这表明您正在与框架作斗争,而不是完全接受它的响应性。
恕我直言,更习惯的“Angular2方式”是这样的:(砰)
@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 :)')
}
}