表达式___经过检查后发生了变化

为什么组件在这个简单的砰砰作响

@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]

当我所做的一切都是更新一个简单的绑定时,我的视图被启动?

418168 次浏览

首先,注意这个异常只会在你以开发模式运行应用程序时被抛出(这是beta-0的默认情况):如果你在引导应用程序时调用enableProdMode(),它不会被抛出(参见更新的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 :)')
}
}

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

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

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

ngAfterViewInit() {
setTimeout(() => {
this.updateMessage();
}, 1);
}
你也可以使用rxjs Observable.timer函数创建一个定时器,然后更新订阅中的消息:           
Observable.timer(1).subscribe(()=> this.updateMessage());

正如drewmoore所说,在这种情况下,正确的解决方案是手动触发当前组件的变更检测。这是使用ChangeDetectorRef对象的detectChanges()方法(从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();
}


}

这里还有Plunkers演示ngOnInitsetTimeoutenableProdMode方法以防万一。

你也可以把你对updateMessage()的调用放在ngOnInt()-方法中,至少它对我有用

ngOnInit() {
this.updateMessage();
}

在RC1中,这不会触发异常

你只需要在正确的生命周期钩子中更新你的消息,在这种情况下是ngAfterContentChecked而不是ngAfterViewInit,因为在ngAfterViewInit中对变量消息的检查已经开始但还没有结束。

< p >看: https://angular.io/docs/ts/latest/guide/lifecycle-hooks.html !# afterview < / p >

所以代码就是:

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上的演示工作

我通过从angular core中添加ChangeDetectionStrategy来解决这个问题。

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

文章你需要知道的关于ExpressionChangedAfterItHasBeenCheckedError错误的一切详细解释了这种行为。

你设置的问题是ngAfterViewInit生命周期钩子是在更改检测处理DOM更新后执行的。你在这个钩子中有效地改变了模板中使用的属性,这意味着DOM需要重新渲染:

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

这将需要另一个变更检测周期,而Angular在设计上只运行一个摘要周期。

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

  • 使用模板中引用的setTimeoutPromise.then或异步可观察对象异步更新属性

  • 在DOM更新之前执行钩子中的属性更新- ngOnInit, ngDoCheck, ngAfterContentInit, ngAfterContentChecked。

它抛出一个错误,因为你的代码在ngAfterViewInit ()被调用时被更新。意思是当ngAfterViewInit发生时你的初始值被改变了,如果你在ngAfterContentInit ()中调用它,那么它不会抛出一个错误。

ngAfterContentInit() {
this.updateMessage();
}

ngAfterViewChecked()对我有用:

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


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

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

有一件事要注意! 如果在组件上添加changeDetection: ChangeDetectionStrategy。OnPush不起作用,并且它是一个子组件(哑组件),也可以尝试将它添加到父组件。< / p >

这修复了bug,但我想知道这有什么副作用。

为此,我尝试了上面的答案,其中许多在最新版本的Angular(6或更高版本)中都不起作用。

我正在使用材料控制,需要在第一次绑定完成后进行更改。

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

加上我的答案,这有助于解决一些具体问题。

出现此错误是因为现有值在初始化后立即更新。所以如果你将更新新值后,现有的值在DOM中呈现,那么它将工作得很好。如本文中提到的Angular调试“表达式在被检查后发生了变化”

比如你可以用

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如何调用这个概念(什么是事件循环?)

在使用数据表时,我得到了类似的错误。当你在另一个*ngFor数据表中使用*ngFor时,会在它拦截角度变化周期时抛出这个错误。因此,不要在数据表内部使用数据表,而是使用一个常规的表或替换mf。带有数组名的数据。这很好。

我认为最简单的解决方法如下:

  1. 做一个给变量赋值的实现,即通过函数或setter。
  2. 在此函数存在的类中创建类变量(static working: boolean),每次调用该函数时,只需将其设为true即可。在函数中,如果working的值为true,则直接返回,不做任何操作。否则,执行您想要的任务。确保在任务完成后将此变量更改为false,即在代码行的末尾或在订阅方法中,当你完成赋值时!

我从AfterViewInit切换到AfterContentChecked,它为我工作。

流程如下

  1. 在构造函数中添加依赖项:

    constructor (private cdr: ChangeDetectorRef) {}

  2. 和调用你的登录在实现方法代码这里:

     ngAfterContentChecked() {
    this.cdr.detectChanges();
    // call or add here your code
    }
    

在我的例子中,它发生在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)也解决了这个问题

我有几乎相同的案例,我有一系列的产品。我不得不让用户删除产品根据他们的选择。最后,如果数组中没有产品,那么我需要显示取消按钮而不是返回按钮,无需重新加载页面。

我通过检查ngAfterViewChecked()生命周期钩子中的空数组来完成。 这就是我是如何做到的,希望它有助于:)

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


products: Product[];
someCondition: boolean;


constructor(private cdr: ChangeDetectorRef) {}


ngAfterViewChecked() {
if(!this.someCondition) {
this.emptyArray();
}
}


emptyArray() {
this.someCondition = this.products.length === 0 ? true : false;


// run change detection explicitly
this.cdr.detectChanges();
}


removeProduct(productId: number) {
// your logic for removing product.
}
< p >好答案。然而,在我看来,当我使用: 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()函数和一些延迟/超时值,如果你需要等待一些事情发生,例如给用户几秒钟。

希望这对你们中的一些人有用。

我有同样的错误,我可以通过使用AfterViewInitChangeDetectionStrategy.OnPush来解决它

这是一篇详细的文章。 https://medium.com/@bencabanes/angular-change-detection-strategy-an-introduction-819aaa7204e7 < / p >

简单:首先分离/删除组件构造中的变更检测,然后在ngAfterViewInit()方法中启用detectChanges()

constructor(private cdr: ChangeDetectorRef) {
this.cdr.detach() // detach/remove the change detection here in constructor
}




ngAfterViewInit(): void {
// do load objects or other logics here
  

// at the end of this method, call detectChanges() method.
this.cdr.detectChanges(); // enable detectChanges here and you're done.
}

你也可以试着把this.updateMessage();放在ngOnInit下面,就像这样:

ngOnInit(): void {
this.updateMessage();
}