Angular: ExpressionChangedAfterItHasBeenCheckedError:在ng 4上对其进行检查后,表达式已更改

创建于 2017-06-16  ·  201评论  ·  资料来源: angular/angular

我正在提交...


[ ] Regression (behavior that used to work and stopped working in a new release)
[X ] Bug report #14748 
[ ] Feature request
[ ] Documentation issue or request
[ ] Support request => Please do not submit support request here, instead see https://github.com/angular/angular/blob/master/CONTRIBUTING.md#question

当前行为


使用异步在模板中使用可观察对象后,我收到:
错误错误:ExpressionChangedAfterItHasBeenCheckedError:检查表达式后,表达式已更改。 上一个值:“”。 当前值:“某物”

预期行为

请告诉我们您的环境

<br i="15"/>
Angular version: 4.2.2

<p i="16">Browser:</p>

<ul i="17">
<li i="18">[X] Chrome (desktop) version Version 58.0.3029.110 (64-bit)</li>
</ul>

<p i="19">For Tooling issues:</p>

<ul i="20">
<li i="21">Node version: v6.10.3</li>
<li i="22">Platform: Mac</li>
</ul>
core regression bufix

最有用的评论

从4.1.3升级到4.2.3后,我遇到了同样的问题。 使用setTimeout可以解决此问题。

从:

PanictUtil.getRequestObservable()。subscribe(data => this.requesting = data);

至:

PanictUtil.getRequestObservable()。subscribe(data => setTimeout(()=> this.requesting = data,0));

所有201条评论

使用redux遇到同样的问题。 这是我在其资料库上发布的问题的链接。 当在角度2上时没有错误,但是在角度4.2.2上,在检查了错误之后我得到了表达式更改。
https://github.com/angular-redux/store/issues/428

从4.1.3升级到4.2.x时,问题出现在我的组件上。 恢复到4.1.3可以为我解决问题,但这不是我想要永远保留的东西。

我可以在一个最小的项目上进行复制,但我仍在尝试找出如何解决此(也许)回归问题。 但是看起来脏支票已经在开发人员中执行了。 从4.2.x开始的模式。 但是更改日志尚不清楚。 任何提示欢迎。

从4.1.3升级到4.2.3后,我遇到了同样的问题。 使用setTimeout可以解决此问题。

从:

PanictUtil.getRequestObservable()。subscribe(data => this.requesting = data);

至:

PanictUtil.getRequestObservable()。subscribe(data => setTimeout(()=> this.requesting = data,0));

@jingglang您能否以尽可能少的代码尝试在http://plnkr.co/edit/tpl:AvJOMERrnz94ekVua0u5上重现该问题?

我遇到了同样的问题-从2.4升级到4.2,并且某些隐藏/显示逻辑现在已损坏。 这是手风琴风格的逻辑,其中可见性取决于将当前面板与可观察到的观察者进行比较,该观察者持有哪个面板应该是可见的(以及其他)。 该组件使用@select来获取可观察的内容,并与|绑定。 模板中的异步代码-在2.4中像超级按钮一样工作,现在获取ExpressionChangedAfterItHaHasBeenCheckedError,并且面板不会在第一次单击时隐藏和显示,而仅在第二次单击时隐藏和显示。

将4.1.3更新为4.2.2时遇到相同的问题。

但是,我找到了解决方法。
注入ChangeDetectionRef ,并在错误点调用detectionChanges()函数。

constructor(private cdr: ChangeDetectionRef) {
}

onChange(): void {
  this.val += 1; // <- get error after
  this.cdr.detectionChanges();
}

认为这与4.2中的后续更改有关。

https://github.com/angular/angular/pull/16592

解决此问题: https :

ng-content标签内是否有任何内容?

我在ng-content标记内的表单存在相同的问题,现在出现ContentChangedAfter错误。

将4.1.3更新为4.2.0或更高版本时遇到相同的问题

我附加了一个小项目来重现该错误

app-error.zip

接下来,我降级到4.1.3(如上所述),错误消失了,因此无论发生什么冲突,它特定于4.2。 我本周(或下一个)没有时间整理一个plunkr,但它似乎与一个动作有关,它更新了商店中两个独立的可观察对象,每个对象都对UI产生了影响。 用户界面更新之一发生,另一个导致异常。

嗯,
绝对回滚到4.1.3版可以解决此问题,并且还可以应用超时,如@jingglang所示。

嗨@tytskyi。 我为此问题做了一个http://plnkr.co/edit/XAxNoV5UcEJOvsAbeLHT 。 希望会有所帮助。

@jingglang提供的解决方法(对我而言)在4.2.0或更高版本上有效
或者也将子项的发出事件更改为其构造函数,不再引发错误

@umens在检查父组件之后更新它,因此您不会保持单向数据流

@alexzuza如何实现? 我一直这样做都是没有错误的。 为什么放置SetTimout不再引发错误。 (我更新了plunkr)我怀疑它是否在影响生命周期?

@umens谢谢您的时间。 问题是@alexzuza所说的:在检查父组件字段后,您需要对其进行更新。 您的代码大致与此等效: http :
为什么有效? 可能是偶然的旧版本的angular或Rxjs在这里有错误。 您能否告诉您使用了哪个版本? 甚至放到工作的unk夫中?

为什么放置SetTimout不再引发错误。

因为您异步更改字段,所以尊重单向数据流。

让我们从相反的角度来考虑:为什么会引发错误? 变更检测的第一项检查从上到下进行。 它检查绑定到模板的app.toolsConfig值。 然后,它渲染<child-cmp> ,它会同步更新app.toolsConfig 。 渲染完成。 现在,它将运行“更改检测”的第二次检查(仅针对dev模式),以确保应用程序状态稳定。 但是在呈现子级之前的app.toolsConfig不等于之后的app.toolsConfig 。 所有这些都在更改检测的单个“转弯”,“周期”期间发生。

好。 感谢您的详细回答。 我找不到有关子组件生命周期何时发生的信息。
对于我以前使用的“工作”版本:
@ angular / *:4.1.1(compiler-cli-> 4.1.0除外)
rxjs:5.3.1

@umens是您提到的旧版本的再现错误: http : //plnkr.co/edit/kfoKmigXzFXwOGb2wyT1?p=preview 它抛出,因此可能是某些第三方依赖性影响了行为或未完成复制指令?

无法分辨。
如果您有兴趣进一步了解,这里是我的yarn.lock: https ://pastebin.com/msARLta1
但我不知道有什么不同

4.1.2切换到4.2.3之后,我看到类似的“ ExpressionChanged ...”错误。

就我而言,我有两个通过服务交互的组件。 该服务是通过我的主模块提供的,并且ComponentAComponentB之前创建。

4.1.2 ,以下语法可以正常工作而不会出现错误:

ComponentA模板:

<ul *ngIf="myService.showList">
...

ComponentB类:

ngOnInit() {
  this.myService.showList = false; //starts as true in the service
  ...
}

4.2.3 ,我必须按如下所示修改ComponentA的模板,以避免出现“ ExpressionChanged ...”错误:

ComponentA模板:

<ul [hidden]="!myService.showList">
...

我仍在尝试弄清楚为什么这只是一个问题,为什么从*ngIf切换到[hidden]起作用。

我也有同样的问题,这种情况通常发生在我使用如下异步管道时:
<div>{{ (obs$ |async).someProperty }}</div>
如果我这样使用它:

<div *ngIf="obs$ | async as o">
  <div>{{ o.someProperty }}</div>
</div>

然后错误消失了,所以我不确定这仅仅是因为很多人犯了一些以前未发现的错误:我认为是4.2中引入了一个错误…

对我来说同样的问题。 问题似乎来自@ angular / router

我也得出结论,它与路由器有关。 试图昨天发布尽可能多的帖子,但看起来发布失败。 我进一步研究并简化了代码,以查看是否可以找到错误。 当操作的最终来源是单击面板标题时,以下代码可完美工作,但通过路由触发时,失败并显示ExpressionChanged错误。

<span class="glyphicon pull-right" [ngClass]="{'glyphicon-chevron-up' : (ui$ | async)?.visiblePanels[VisiblePanel.IngredientTypes], 'glyphicon-chevron-down' : !((ui$ | async)?.visiblePanels[VisiblePanel.IngredientTypes])}"></span>

帮助我解决了问题! 您必须在父级中显式触发更改。

就像@acidghost一样,我可以通过在父组件的AfterViewInit运行ChangeDetectorRef#detectChanges来解决此问题。

如果您有plunkr repro,我愿意进行检查,因为我们对gitter也有一些类似的抱怨,而且最终总是成为用户问题。

无法确定是在4.2中引入了错误还是在<4.2上缺少错误并在4.2上得到解决。

错误:ExpressionChangedAfterItHasBeenCheckedError
解:
componentRef.changeDetectorRef.detectChanges();

以下是我的代码(动态生成的组件)的一部分:
componentRef = viewContainerRef.createComponent(componentFactory); //组件已创建
(componentRef.instance).data = input_data; //在此手动更改组件数据
componentRef.changeDetectorRef.detectChanges(); //将导致更改检测

对于简单的组件,只需google如何访问“ componentRef”,然后执行
componentRef.changeDetectorRef.detectChanges();
在设置/更改组件数据/模型之后。

解决方案2:
打开对话框“ this.dialog.open(DialogComponent)”时,我再次遇到此错误。
因此将打开的对话框代码放入函数中,然后在该函数上应用setTimeout()解决了该问题。
例如:
openDialog(){
让dialogRef = this.dialog.open(DialogComponent);
}
ngOnInit():void {
setTimeout(()=> this.openDialog(),0);
}
虽然看起来像hack ..工程!!!

从4.2开始,我遇到了同样的问题,没有解决方案或解决方法对我有用:(我在ngAfterViewInit方法中使用组件工厂动态创建了一个组件。该组件使用的指令具有@Input()字段。我将其绑定指令的@Input()字段似乎是未定义的,直到构建和检查组件的视图,然后才使用静态字符串对其进行初始化。

https://plnkr.co/edit/E7wBQXm8CVnYypuUrPZd?p=info

staticComponent.ts

export class StaticComponent implements AfterViewInit {
  @ViewChild('dynamicContent', {read: ViewContainerRef})
  dynamicContent: ViewContainerRef;

  constructor(private componentFactoryResolver: ComponentFactoryResolver) {}

  ngAfterViewInit(): void {
    const componentFactory: ComponentFactory<DynamicComponent> = this.componentFactoryResolver.resolveComponentFactory(DynamicComponent);
    this.dynamicContent.createComponent(componentFactory);
  }
}

测试指令

@Directive({
  selector: '[testDirective]'
})
export class TestDirective {
  @Input()
  testDirective: string;
}

动态组件

@Component({
  selector: 'dynamic-component',
  template: `<div [testDirective]="'test'">XXX</div>`
})
export class DynamicComponent {
}

错误错误:ExpressionChangedAfterItHasBeenCheckedError:检查表达式后,表达式已更改。 上一个值:“未定义”。 当前值:“测试”。 似乎已在对其父级和子级进行脏检查后创建了该视图。 它是在变更检测挂钩中创建的吗?

我有点困惑阅读此线程。 这是一个已知错误,将在以后的版本中解决吗? 或者,这是预期的行为吗? 如果可以,解决方案是什么? 谢谢。

不知道它是否相关,但是我非常讨厌这个消息! 仅当使用开箱即用的“ ng test”命令进行单元测试时,这才对我显示。 我想我可能对如何成功模拟简单服务感到困惑。 同样令人惊讶的是,没有其他人在测试中看到这一点,找不到与测试相关的任何东西。 我将观看此主题以及任何对ExpressionChangedAfterItHaHasBeenCheckedError +单元测试的搜索。

我一直在尝试将plnkr组合在一起,发现很难重现该错误-但是,我认为,广泛的调试使我更加了解问题出在哪里(尽管并不能确定它是否是一个错误,或我的错误)。

我使用缩小版本的相同reducer的降级版本(与真实应用程序中的顺序相同)组合构建了一个小示例应用程序,最后是routerReducer。 令我沮丧的是,没有发生错误。 因此,我设置了断点,并观察了减速器中动作的流程。 我发现这两个应用程序以相反的顺序调度动作。

我的UI reducer管理可见性,并且是触发错误的状态更改的来源。 这是reducer的简化版本,显示了相关操作:


export const userInterfaceReducer = (state = INITIAL_UI(), action: any) => {
    switch (action.type) {
        case IngredientActions.LIST_INGREDIENTS:
            if (action.hideTypes) {
                return Object.assign(state, { visiblePanel: VisiblePanel.Ingredients });
            }
            return state;
        default:
            return state;
    }
}

当我在小型测试应用中运行此程序时,它可以工作-首先,将触发LIST_INGREDIENTS操作并返回新状态; 然后会触发@ angular-redux / router :: UPDATE_LOCATION操作,并命中默认情况,并返回相同状态。

当我在实际的应用程序中运行它时,顺序或操作是相反的。 @ angular-redux / router :: UPDATE_LOCATION操作首先触发,从默认状态返回旧状态。 然后激发LIST_INGREDIENTS,返回新的(更改的)状态-并引发错误。

我完全同意我做过一些愚蠢的事情,但这不是一个实际的错误-但是,如果是这样,其他人是否会看到我的所作所为?

(作为一个脚注,我检查了4.1.3版本...,它也以“正确”的顺序触发了redux减速器-也就是说,位置更改在列表成分操作之后触发,这大概是它起作用的原因,但是4.2版没有)。

当一个组件更改服务的属性时,会遇到同样令人讨厌的错误。 通过将* ngIf移到父组件来解决。 所以绝对是一个错误

可以使用ngDoCheck()方法并调用ChangeDetectorRef.detectChanges方法来解决此问题。

public ngDoCheck(): void {
    this.cdr.detectChanges();
}

@pappacurds不要那样做。 您已经造成了无限的变更检测周期。

在这里可能是相同的错误https://plnkr.co/edit/IeHrTX0qil17JK3P4GBb?p=preview

错误: previous undefined after undefiend

更新后出现同样的错误

我也是

升级到4.2.6后,在4.0.0中一切正常,我得到了相同的错误。

有关ExpressionChangedAfterItHasBeenCheckedError错误的所有信息,您需要深入了解为什么会发生此错误以及可能的修复方法

修复是显而易见的,但是更改框架的方式使得更新导致用户应用程序被重写为“支持”,从而连接了新的次要平台更新。 JS仍然保持旧错误以保持兼容性...

这里同样的错误

我还在~4.2.4看到此问题。 降级为~4.1.3可解决此问题。

问题在于更改了ContentChildren ,即使在父级(进行更改的位置) AfterViewInit的末尾使用ChangeDetectorRef detectChanges()时也是如此。 奇怪的是,此问题仅在组件而不是指令中发生。 我将在4.2.4中使用的指令转换为带有<ng-content></ng-content>模板的组件,然后开始抛出ExpressionChangedAfterItHasBeenCheckedError错误。

我在使用Material 2输入控件时得到了这个。 试图设置它的价值

ngAfterViewInit(): void {
this.numberFormControl.setValue(200);
}

我整理了一个简单的Plunker,显示了一个情况,其中ExpressionChangedAfterItHasBeenCheckedError错误仅从Angular 4.2.x

我创建了一个简单的插件,即使在父组件的ngAfterViewInit使用ChangeDetectorRef detectChanges()时,更改ContentChild的值时也会显示错误。

编辑:根据我的第一个示例,还创建了一个简单的插件,但使用父指令而不是Component且没有错误。

@saverett我看到了与您的

@JSMike我的情况与您的plunker完全相同(通过在setTimeout中将引用ContentChild代码包装在plunker中来解决此问题

@JSMike感谢您的注意。 似乎与angular / zone.js#832有关。 我现在将zone.js版本设置为0.8.12,直到他们修复了最新的zone.js。 柱塞应重新工作。

@matkokvesic并不是真正的解决方案,只会使更改发生在生命周期挂钩之外。 使用Angular v4.1.3时,提供的代码示例不会导致错误。

@JSMike我同意,这是一个临时解决方法。 在Angular 4.2.6之前,使用ContentChildrenngAfterViewInit中引用的元素可以正常工作。

我通过在代码中使用changeDetection: ChangeDetectionStrategy.OnPushthis.changeDetector.markForCheck();来修复它

@touqeershafi如果您使用this.changeDetector.markForCheck();并不表示您没有以正确的方式使用事物

这仍在发生。 这绝对是一个错误,而不是预期的行为,应予以修复。

@istiti我知道这不是一个正确的方法,但至少可以暂时消除此错误,但是就我的项目而言,我已经在TODO了错误URL。

这仍在发生。 这绝对是一个错误,而不是预期的行为,应予以修复。

我想我必须同意@rwngallego ...在4.2.x更新之前,我已经编写了许多可重复使用的/ ng-content stlye组件,这些组件依赖于@ContentChildren并通过这些组件在父子之间建立关系。渠道(获取/设置属性等)。

我发现自己经常需要将ChangeDetectorRef实例注入所有这些构造函数(包括扩展此组件的子类)中,并且感觉不到预期的行为或正确的行为。

对于使用@saverett之类的动态组件的https://github.com/angular/angular/issues/15634所述的问题

对于任何在ngAfterViewInit / ngAfterViewChecked内部进行更改的人,我认为这是预期的。

对于其他人(这里还没有看到),对我来说仍然是一个谜。

在以下情况下,这对我而言正在发生。
有一个使用子组件的父组件。 子组件需要父组件将提供的数据,但是该数据来自父构造函数中的promise结果。 所以:

父母

constructor {
dataservice.getData().then((result) => {
this.source = result;
});
}

父模板

<app-child [source]="source"></app-child>

子组件

@Input() source:any[];

这段代码抛出了这个错误,但是将其添加到组件中解决了该问题:

constructor(private cdRef: ChangeDetectorRef) {
dataservice.getData().then((result) => {
this.source = result;
 this.cdRef.detectChanges();
});
    }

现在是推荐的解决方案,还是解决错误的方法,进行此类更改将意味着什么? 对我来说,似乎您是在告诉Angular检测更改,我认为您应该避免这样做...

@sulhome ...您的问题是,在CD周期及其在开发模式下的确认期间,父级构造函数中的promise正在更改子级组件上的@Input 。 这就是错误的原因。

this.cdRef.detectChanges();添加了第二个CD循环,因此确认成功后=>没有错误。

因此,一切正常。 重要的是要了解在代码中使用正确的构造和通用逻辑如何避免这些类型的问题。 您可以在此处的深入说明中阅读有关内容: https :

@ mlc-mlapis,所以您是说我通过添加this.cdRef.detectChanges();做对了,这没有任何意义吗?
我以为通过在孩子身上ngOnChanges ,那么我应该从父母那里选择ngOnChanges设计的目的。

@sulhome是的, this.cdRef.detectChanges();是有帮助的,但是它的结果是您现在有2张CD ...因此,这取决于在组件上使用OnPush策略的方式和位置来消除开销。再增加一个CD周期。

如果确实dataservice.getData使用http来获取一些数据,则可能最简单的解决方案是在该服务中具有可观察对象,并从子组件中对其进行订阅。 因此,当数据将在流上发出时,孩子可以对此做出反应。

@ghetolay作为记录,在某些情况下是错误,请参阅例如我打开的#18129。

当我使用@Output从子组件获取数据时,出现了这个问题。

在父母陪伴home.ts

drag_second(trigger){ console.log("dragged222222"+trigger); if(trigger){ this.isactive=true; }else{ this.isactive=false; } }
和home.html
<div class="upper" [ngClass]="{'isactive':isactive}">

当dragstart和dragend时,isactive会更改。

错误消息是
ExpressionChangedAfterItHasBeenCheckedError:表达式在检查后已更改。 先前的值:“ true”。 当前值:“ false”。

还是有其他方法可以更改/添加类属性?

在4.2.x和4.3.0中存在相同的问题,但在4.1.x中没有。

仅在使用ng-template路由上发生错误。

解决方案:ngAfterViewInit所有代码放入setTimeout为我解决了这个问题(感谢@jingglang)。

public ngAfterViewInit(): void {
    setTimeout(() => {
        //my code
    });
}

@Wernerson ,看看JSMike对setTimeout

我的问题是我使用ActivatedRouteparams Observable来修改ngOnInit()中的某些状态。 似乎Router发出params同步,因此更新发生在CD期间。
我对此的简单解决方案是:

  // !!! see delay(0)
  // results are consumed by async Pipe, where I also had the CD issue (null vs. undefined)
  this.results = this._activatedRoute.params.delay(0)
    .do(params => this.searchParameters = this._createSearchParameters(params))
    .switchMap(p => {
      let key = this.searchParameters.key();
      return this._searchResultCache.results.map(r => r.get(key));
    });

(我知道这不是有史以来最漂亮的反应式/功能代码;))
因此解决方案是将参数发射delay(0)tick

希望我能为遇到类似问题的其他人提供帮助。
也欢迎任何担忧,我的解决方案是错误还是危险的。

此问题的状态如何? 在Angular 4.2.6中仍然很明显。
这是生产环境中的问题吗? 由于此错误只会在调试版本中发生。

我讨厌手动运行更改检测甚至在setTimeout()中运行它的所有解决方法...

setTimeout()效率低下,因为它会导致整个应用程序出现新的变更检测周期,而detectChanges()仅检查当前组件AFAIK。

我的团队对ngOnInit是什么和不是什么有一个核心的误解。 医生说

ngOnInit() :在Angular首先显示数据绑定属性并设置指令/组件的输入属性后初始化指令/组件,在第一个ngOnChanges()之后调用一次。

我们正在从ngOnInit订阅我们的服务(跟踪应用程序状态)。 我们的订阅是同步的BehaviorSubjects; 他们在订阅时触发。 因此,“在Angular首先...设置指令/组件的输入属性之后”,我们立即更改了值。 这是一个非常简单的Plunker,说明了这是一个问题。

Tour Of Heroes中的示例最终使用http,它是异步的,因此不会引起问题。 我认为这是我们困惑的根源。

我们现在要做的是改为从构造函数中订阅同步状态,以便立即设置组件的值。 该线程中的其他人建议通过超时启动第二次更改检测,稍后再设置值,或明确告诉angular这样做。 这样做可以防止错误出现,但是恕我直言,这可能是不必要的。

@maximusk共享的文章是必读的您需要了解有关ExpressionChangedAfterItHasBeenCheckedError error的所有信息

如果没有其他问题,此线程应在正式的Angular文档中导致所有警告。

@maximusk共享的文章是必读的:您需要了解有关ExpressionChangedAfterItHasBeenCheckedError错误的所有信息。

谢谢

@adamdport

在使用@ViewChildren @ContentChildren修改父级的子级属性的情况下,这是否可行,因为在执行ngAfterViewInit()生命周期之前,这些子级才可用?

请记住,此问题的一部分是已确认的错误,而不是用户的错误:
https://github.com/angular/angular/issues/15634

@saverett解决的问题是正确的,使用*ngIf导致了错误。

*观看...

如果将对象传递到“ Angular Material”对话框,然后在对话框中直接使用它们而不是执行this.myDataInput = Object.assign({}, dialogDataInput)则会发生这种情况。 即this.myDataInput = dialogDataInput;然后执行类似this.myDataInput.isDefault = !this.myDataInput.isDefault; 。 像上面解释的那样,子组件(对话框)正在更改在父组件视图中公开的值。

用[attribute]替换属性

有谁能确定这是预期的错误还是错误?
在ngOnInit中加载数据是否正确? 因为如果我将代码移至构造函数(通过Observable加载),则不会引发该错误...

@Wernerson 。 您的HTML中是否有* ngIf? 请注意,在ngOnInit完成后,事物将被渲染,因此* ngIf中的所有内容将无法正常工作,并可能导致此错误。

它必须被重做。 使用“ ngIf”和自定义控件,您会收到此错误消息。 超时当然不是解决方案。

@ j-nord如果您阅读了评论,便会知道不需要timeout

@ j-nord / @dgroh我也经历过同样的

@Wernerson是的,此问题的一部分是预期的错误或错误: https :

@ mawo87对于隐藏起来对我有用,因为它不会从DOM中删除元素。 对于我们的要求,这还可以,也是一种干净的解决方案。

@ mawo87
首先,您必须找到错误消息所指的变量。 @Angular Team :最好在错误消息中包含变量的名称。 用较大的遮罩和Webpack很难找到这一点。 如果您在该命令的位置找到了该命令,那么以下行中的命令将有所帮助:

this.cdr.detectionChanges();

请参阅来自6月19日的vermilion928的示例

谢谢@dgroh和@ j-nord。
@ j-nord我也在查看detectionChanges方法,但是我可能不使用这种方法,因为在下面的文章中建议不要这样做:
https://hackernoon.com/everything-you-need-to-know-about-the-expressionchangedafterithasbeencheckederror-error-e3fd9ce7dbb4

就我而言,我使用的是PageTitle服务,并在父组件中订阅它,然后在子组件加载到视图中时,它们在服务中设置PageTitle,而父组件获取值并通过订阅显示它。 这在4.1.x中没有任何错误,但在4.2x及更高版本中引发了ExpressionChangedAfterItHasBeenCheckedError错误。

一切仍然按预期进行,并且错误不会在已部署/生产版本中显示(由于缺少第二个变更检测周期),但是在开发中,控制台中有很多红色。

我仍然不清楚什么是适当的(甚至是短期的)修复方法。 我的父组件订阅位于构造函数中,而子组件通过服务进行的更新位于ngOnInit中。

我已经阅读了“您需要了解的所有信息...”一文,内容丰富,但并没有使我相信我做错了任何事情,也没有提供解决该问题的有效方法。

@alignsoft如果您要在ngOnInit的子组件中设置页面标题,那么我认为该错误是有效的...请参阅之前链接的文章。

@alignsoft我同意,围绕此错误已进行了很多讨论,但没有关于其是否有效的明确的指示……这更多是对开发人员的有关标准和数据绑定/操作的“警告”吗?

在我的特定情况下,我正在使用@ViewChild@ContentChild处理父/子组件,必须一遍又一遍地调用detectChanges()以阻止错误在控制台中显示...

@rolandoldengarm我尝试在父构造函数中进行预订,并在子组件中的AfterViewInit中设置值,但仍然收到错误。 这是基于流程和更改检测周期来解决此问题的正确方法,并且该错误可能是错误?

拜托,在这里有某种正式的答案将是很棒的...

再看这个。 看起来像我来自https://github.com/angular/angular/issues/17572#issuecomment -315096085的示例
尝试在组件生命周期中尝试操作ContentChildren时,应该一直使用AfterContentInit而不是AfterViewInit

这是带有AfterContentInit的更新的plnkr,没有更多错误: http ://plnkr.co/edit/rkjTmqclHmqmpuwlD0Y9?p=preview

@JSMike我将AfterViewInit更改AfterContentInit ,仍然是相同的错误。 即使我在OnInit内更新数据,也会发生错误。 据我所知,这是根据Angular生命周期正确(对吗?)在OnInit期间初始化数据。 再加上在以前的版本中未发生此错误,我认为这是一个错误。 但是,如果是这样的话,我想向开发团队发表一些声明。 :D

@Wernerson,您不应该在ngOnInit内部更新数据,除非该生命周期挂钩中有可用数据。 如果与内容子级有关,则应将逻辑移到ngAfterContentInit

@JSMike没有帮助,该错误仍然发生。 我只是在ngAfterContentInit收到(异步)请求的数据后,才在应用程序中订阅状态更改。

@Wernerson您有一个plnkr示例吗?

StackBlitz的基本样本
发出事件的子组件和更新局部变量的父组件。
在js控制台中获取错误:
ExpressionChangedAfterItHasBeenCheckedError: Expression has changed after it was checked. Previous value: 'undefined'. Current value: 'hello world'.

发现, ngAfterContentInit仅在子项为静态的情况下有效,使用*ngFor http://plnkr.co/edit/rkjTmqclHmqmpuwlD0Y9?p=preview创建子项时仍然失败

@soldiertt你只需要detectChanges() https://stackblitz.com/edit/angular-qfu74y

@JSMike如果我这样做,则不会触发子组件的ngAfterViewInit: https ://stackblitz.com/edit/angular-bdwmgf

@soldiertt看起来很奇怪,但是看起来像一个单独的问题,应该报告。

@JSMike我的设置相当复杂,包含服务,第三方库,机密信息等。不幸的是,我无法重现错误:/

注入ChangeDetectorRef为我工作!

我在轮播中遇到了这个问题,因为我需要在轮播项目组件上设置一些属性,该属性只有在父轮播组件中计算后才能知道。 而且所有项目都通过ng-content传递,因此我只能使用父轮播组件中ngAfterContentInit中的项目。 就像是:
轮播:

@ContentChildren(ItemComponent) carouselItems;

ngAfterContentInit() {
  // ... some computations
  this.carouselItems.forEach((item: ItemComponent, currentIndex) => {
    item.setPropertyX(someComputedValue);
    // OR:
    item.x = someComputedValue; // of course, this would work as well
  });
}

项目:

setPropertyX(value) {
  this.x = value;
}

除了setTimeout() hack之外,唯一的其他解决方案是使用服务和主题,使商品与父轮播进行通信吗? 我的意思是荒谬……那意味着,如果轮播中我有100个项目,那么我将有100个订阅。 如果我有2个转盘,那翻了一番。 肯定不是推荐的方法吗?! 对?

通常,我们需要在组件上设置一些属性,这些属性在AfterContentInit之前是未知的...而且在许多情况下(当我们不谈论1或2个组件时,而是只有十分之一或数百个订阅,而是像轮播中的50或100),这是荒谬的。

Angular确实有一些很酷的地方可以帮助我,但是我还没有发现?

PS我还尝试注入ChangeDetectorRef并在轮播组件和项目组件上使用detach() 。 我本来希望这样可以解决该错误,因为……我基本上是说“ _screw更改检测,我实际上根本不需要它。拜托,相当请:不要检测到任何更改_” ...但是仍然抛出错误! 那么,如果detach()方法没有按照其说的去做,那又有什么意义呢?

constructor(private cdr: ChangeDetectorRef) {
  this.cdr.detach();
}

setTimeout()不一定是黑客,它可能是一种非常有效的处理方式,例如,当您对视图进行更新时做出反应,例如:在ngAfterViewInit期间更改模板的绑定。

问题是当您将其用作魔术而不真正知道为什么时。 但是在完全有效的情况下,使用setTimeout()是正确的方法。

@ghetolay突破变更检测系统正在评估的组件的生命周期,这不是很容易吗? 我一直在使用ChangeDetectorRef.detectChanges()路线,但是对我来说,这简直就像是骇客...如果这些解决方案中的任何一个都是“可以接受的”,我想我们将不得不接受它,但这确实破坏了任何解决方案使用ViewChild(ren)或ContentChild(ren)修改

@fugwenna

打破变更检测系统正在评估的组件的生命周期,不是很容易吗?

我不明白那个问题。 突破生命周期?

每次发生异步事件(dom事件,xhr,setTimeout等)时,Angular都会运行一轮CD,在该CD期间它将更新视图。 ngAfterViewInit钩子在此之后运行,因此不再有计划运行的CD,并且不会再次触发它,直到发生另一个异步事件为止。 使用setTimeout()是再次触发它的最简单方法。 您当然可以自己做,但我认为收益微不足道。

@ghetolay我引用了@JSMike和其他我读过的其他主题的评论:

@matkokvesic并不是真正的解决方案,只会使更改发生在生命周期挂钩之外。 使用Angular v4.1.3时,提供的代码示例不会导致错误。

因此,我认为使用setTimeout()将导致生命周期挂钩被“跳过”或未像通常那样被评估?

@ghetolay @fugwenna ChangeDetectorRefdetach()方法怎么样? 有什么想法吗? 那不应该完全禁用变更检测,从而消除错误吗? 我不明白为什么它不起作用? 还是该错误是“独立的”? 这意味着Angular并不真正在乎我们已禁用更改检测这一事实,它只在乎设置属性后是否更改了属性?
如果人们仍然认为“ ExpressionChangedAfterItHasBeenChecked”是否是一个错误,那么也许这绝对是一个错误...在我看来,如果我们已经完全设置了属性,那么在设置属性后,Angular就不再关心我们更改属性禁用更改检测。 尤其是对于父组件和所有其他子组件,我基本上是在整棵树上完成的。

我在这里有点迷惑,我遇到的问题之一是我不确定在开发模式下不触发异常该如何做一些非常普通的事情。

例如,我编写了一个控制值访问器组件,该组件封装了一个日期选择器。 我为该组件提供了一个空模型,该模型在内部传递给datepicker,该模型对其进行初始化并通过注册的函数将其发送回去。 当然,孩子们会在模型初始化期间修改模型,但这就是我想要的! 我想找回一个初始化的模型,但不想在父级中进行初始化,因为了解日期和日期的是日期选择器。

问题是我现在该怎么做? 调用setTimeout并再次触发更改检测? 使用ChangeDetectorRef吗? 我不喜欢它,因为它确实使代码难以阅读。 在单独的服务中初始化事物? 但是,该服务仅用于非常技术性的目的,没有任何商业价值,我什至不确定这能否解决问题。

@fugwenna我主要关心的是它在4.1.3中有效,现在已损坏。 我还没有看到有人在角队闹铃中问我们是否应该在初始化函数中使用setTimeout并让区域接管,但与以前的工作方式相比,它感觉像是一种反模式。

@ kavi87因此,这个stackoverflow问题实际上为我提供了所需的优雅解决方案。 这是使我的子组件中的事件发射器异步
new EventEmitter(true)

对于标记为带有109条评论的回归的问题,此问题与任何正式用语相比都是令人失望的。

虽然这里的每个人都在使用自助技术方面做得很好,但我很想知道这是否是一个错误,或者只是从4.2.x开始实施的一些新行为? 如果是这样,它应该被列为重大更改(因此不是次要版本吗?)

@MrCroft
我不确定完全禁用变更检测以使异常静默是否是解决问题的好方法,尽管从理论上讲,我知道它应该/如何有意义。

我同意@JSMike@rosslavery的意见,对于为什么此错误消息来自于4.2x之后而没有任何破坏性变化的解释却很少做出回应或解释。但是,另一个令人费解的地方是,它似乎只被抛出了。开发模式,几乎感觉像是中断更改检测的警告(?)

在我的AppComponent中,我有一个div。
该div的唯一目的是显示繁忙的叠加层。
忙状态是在http服务类中触发的:

app.component.html

<!-- Busy Overlay -->
<div *ngIf="busy$ | async"
     class="overlay">
    <i class="loading">
        <i><sk-wave></sk-wave></i>
    </i>
</div>

app.component.ts:

  get busy$(): Observable<boolean> {
    return this.catolService.busy$;
  }

升级到4.3.4后,当服务触发繁忙状态时,我总是收到ExpressionChangedAfterItHaHasBeenCheckedError。

是否有针对此简单异步方案的修复程序?

@mukunku谢谢,我会尝试的。

@ mlc-mlapis我稍微更改了您的插件,我的代码正是https://plnkr.co/edit/uUT7tiM5BKPIy2upJPhb?p=preview

但是,punker没有显示问题。 我将尝试升级到最新的4.x,以查看是否已修复。

谢谢

大家好-抱歉在这里延迟。

所以-这种行为实际上是按预期进行的,我意识到对于某些人来说这可能是出乎意料的,所以让我尝试解释一下机制。

@saveretts“plunker是一个很好的摄制穿行(谢谢!) - https://plnkr.co/edit/fr7rTNTCgEhHcbKnxAWN?p=preview

tldr是Angular一直以这种方式工作-更改检测从上到下(或父级->子级)一次运行。 因此,如果我们有:


@Component({ 
  selector: 'app-root',
  template: `
    <!-- initially falsey -->
    <div *ngIf="sharedService.someValue">NgIf Block</div>
    <child-comp></child-comp>
  `
})
class AppRoot {
  constructor(public sharedService:SharedService){}
}
@Component({ 
  selector: 'child-component',
  template: `child comp`
})
class ChildComponent {
  constructor(public sharedService:SharedService){}
  ngOnInit(){
    this.sharedService.someValue = true;
  }
}

更改检测将首先针对应用程序根目录运行,检查其所有绑定,然后更改检测子组件-再次,因为我们通过次操作(而不是AngularJS风格的检查直到稳定)进行此操作,即父绑定(* ngIf,在这种情况下)不会被重新检查-所以现在您的应用程序处于不一致状态(!)

在开发模式下,我们实际上运行了两次更改检测-第二次运行该更改以验证绑定没有更改(这将指示状态不一致),如果发现此类问题,则会引发错误。 您可以通过在repro中调用enableProdMode()来验证此行为: https ://plnkr.co/edit/GGEzdxK1eHzHyGiAdp56 = preview

最近出现这种情况的原因是,以前,路由器在插入组件时进行了许多其他的更改检测检查-这效率低下,并导致动画和路由问题,尤其是当路由器插座嵌套在ngIfs等中时。

我会辩称,通常,违反这种单向流程的代码(如上述repro所做的那样)是不正确的。 但是-在某些情况下,您可能需要这种行为(实际上,我们在Angular表单库中有类似的设置)。 这意味着您需要显式触发另一次更改检测运行(这将返回到根组件并自上而下检查)。 调用setTimeout可以实现这一点,尽管通常最好使用Promise来触发它-参见https://plnkr.co/edit/IoKMxEOlY9lY9LcWwFKv?p=preview

希望有帮助!

@robwormald在其他情况下也发生了相同的错误。 我不确定ContentChildren是否可以解决此问题。 为什么指令可以在AfterContentInit期间无误地更改子代的值,但是在Component中执行相同的操作会引发错误?

另外,是否期望在一个初始化过程中调用cdr.detectChanges()导致另一个初始化函数最终不被调用? 这是在此线程为此创建了一个单独的问题

@robwormald

我相信@JSMike在评估中是正确的,因为在单向流来自使用ContentChild(ren)和ViewChild(ren)的Parent-> Child的情况下,仍然会抛出此错误。

考虑一下我之前所说的内容是否可以接受,即该错误更多是“警告”,它表明开发人员违反了单向流程“最佳实践”,但未引起任何应用程序中断错误,因为更改检测在生产模式(真实世界)中只能运行一次?

@fugwenna不,这确实是一个错误,因为否则它会使视图处于不一致状态。 Rob正在运行的示例说明了一切。

另外,文档中也有这句话

组成视图后,Angular的单向数据流规则禁止对其进行更新。 组成组件的视图后,这两个钩子都会触发。

如果您确实需要在那里进行任何更改,请使用一种异步方法来进行更改。 setTimeout在这里可能适用,但要注意,在撰写本文时,它本身会延迟4毫秒。 另一种方法是Promise.resolve().then(() => Assignment = here)

更好的是退后一步,看看为什么需要在此过程的后期进行更改,并查看是否有更干净的方法来解决问题。 如果发现自己在执行此操作,则将其视为代码气味。

感谢您对@robwormald的详细解释。 我仍然感到困惑,为什么使用[hidden]绑定而不是*ngIf时子级->父级数据流仍然可以正常工作(请参阅https://plnkr.co/edit/L4QWK5pTgF1qZatFAN4u?p =预览)。 任何想法为什么在没有给出ExpressionChangedAfterItHasBeenCheckedError情况下仍然可以工作?

Angular使用checkAndUpdateView函数执行更改检测。

该函数的简短表示可能类似于:

function checkAndUpdateView(view) {
    // update bound properties and run ngOnChanges/ngOnInit/ngDoCheck for child directives
    updateDirectives
    // check all embedded views that belong current view
    execEmbeddedViewsAction(view, ViewAction.CheckAndUpdate);
    // update DOM for the current view
    updateRenderer
    // perform checkAndUpdate for all child components
    execComponentViewsAction(view, ViewAction.CheckAndUpdate);
}

由于execEmbeddedViewsActionexecComponentViewsAction方法而递归执行。 这也是关键点,在检查指令之后和更新DOM之前,对嵌入式视图进行角度检查。

为了简化@saverett示例,我将使用以下模板

<div *ngIf="!sharedService.displayList">Some text</div>
<router-outlet></router-outlet>

https://plnkr.co/edit/OdkzHjEXbEd3efYAisFM?p=preview

我们已经知道*ngIf只是糖。 所以

<ng-template [ngIf]="sharedService.displayList">
  <div>Some text</div>
</ng-template>
<router-outlet></router-outlet>

我们可以想象以下结构:

  my-app
     |
   NgIf directive
       NgIf EmbeddedView
   RouterOutlet directive

RouterOutlet指令做什么?

它将路由组件注入ViewContainerRef

activateWith(activatedRoute: ActivatedRoute, resolver: ComponentFactoryResolver|null) {
    ...
    this.activated = this.location.createComponent(factory, this.location.length, injector);

这意味着router-outlet创建EmbeddedView因此在RouterOutlet指令初始化之后,我们在AppComponent视图中有两个嵌入式视图。

  my-app
     |
   NgIf directive ([ngIf]="sharedService.displayList")
       NgIf EmbeddedView
   RouterOutlet directive 
      BComponent EmbeddedView

让我们在图片中说明

haschanged

现在让我们回到主要功能

function checkAndUpdateView(view) {
    // update bound properties and run ngOnChanges/ngOnInit/ngDoCheck for child directives
    updateDirectives
    // check all embedded views that belong current view
    execEmbeddedViewsAction(view, ViewAction.CheckAndUpdate);
    ...
    // update DOM for the current view
    updateRenderer
    ...
}

Angular将按以下顺序执行检查:

NgIf directive ([ngIf]="sharedService.displayList") updateDirectives(1)
      NgIf EmbeddedView   execEmbeddedViewsAction(2)
RouterOutlet directive 
      BComponent EmbeddedView  execEmbeddedViewsAction(3)

我们的NgIf指令将首先被检查(这里会记住sharedService.displayList=false ),然后将调用BComponent NgOnInit方法(我们在其中更改sharedService.displayListtrue ),当angular执行checkNoChanges ,将引发错误Expression has changed after it was checked

然后,将*ngIf替换[hidden] 。 现在,我们仅会在RouterOutlet插入一个嵌入视图,并且将在updateRenderer内检查我们的[hidden]="sharedService.displayList" updateRenderer

  my-app
     |
   div{Some Text} just a node
   RouterOutlet directive 
      BComponent EmbeddedView

Angular将执行以下检查:

function checkAndUpdateView(view) {
    // check all embedded views that belong current view
    execEmbeddedViewsAction(view, ViewAction.CheckAndUpdate);   BComponent.ngOnInit (1)
    ...
    // update DOM for the current view
    updateRenderer   update hidden binding (2)
    ...
}

这种方式的dom属性绑定不会导致错误,因为我们在调用ngOnInit值后进行了更新。

这是结构指令*ngIf和dom属性绑定[hidden]之间的主要区别

感谢您的详细概述@alexzuza! 这非常有帮助!

setTimeout()实际上为我工作了:)谢谢@jingglang :)
.subscribe(data => setTimeout(()=> this.requesting = data,0))

@robwormald在这里是正确的。 我遇到了同样的问题,由于我使用的是NGRX和Observable,因此很难检测到。 我遇到的问题是,路线层次结构中有不同级别的组件容器正在订阅存储状态。 将容器组件移到功能模块中的同一级别后,问题就消失了。 我知道这是一个令人费解的答案,但是如果您碰巧正在使用NGRX容器/组件模式,那么这有望将您的研究指向正确的方向

使用setTimeOut可以工作,谢谢@jingglang
ngAfterViewInit(){
this.innerHeight =(window.innerHeight);
setTimeout(()=> this.printPosition(),0);
}

@alexzuza ,这不仅发生在ngIf上,[ngClass]也发生(至少对我来说)

@ Ninja-Coding-Git我刚刚描述了具体情况。 我知道任何代码都可能发生这种情况。

对我来说,更改元素后调用ChangeDetectorRef解决了问题:

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

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

  constructor(private _cdr: ChangeDetectorRef) {}

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

}

解决方案在这里找到

你好@thetylerb! 您能详细说明一下吗? 我们正在使用ngrx,我只是不知道该如何补偿。 我认为在可观察变量之上使用promise非常尴尬。

@ piq9117您也可以看看此评论https://github.com/angular/angular/issues/18129#issuecomment -326801192我对变更检测和ngrx之间的交互进行了研究,我想这可能会有所帮助

@victornoel谢谢! 这几乎描述了我们的情况。 我们得到了嵌套的不可变数据结构(immutablejs),并且到处都在使用异步管道。 我最初的想法是,当商店水化后,商店将更改组件树,而组件树将仅补偿此更改并进行另一次更改。

我们得到了嵌套的不可变数据结构(immutablejs),并且到处都在使用异步管道。 我最初的想法是,当商店水化后,商店将更改组件树,而组件树将仅补偿此更改并执行另一个changeDetection。

在我的项目中,我们有某种标准化的数据结构(而不​​是嵌套的数据结构),因此它可以缓解此类问题,直到没有! :O

您唯一的希望是:

  • 更改您存储的数据结构,以便更改一个部分不会触发对其他部分的更新
  • 注意不要从通过异步管道监听存储的组件的子组件触发事件
  • 触发上述事件时触发手动更改检测
  • 在另一个时间或在setTimeout中触发这些事件

我正在使用ngrx并遇到如下情况:

在app.component.html中:
<div *ngIf='(appState | async).show'>test</div>
并在app.component.ts中
this.appState= this.store.select("app");

当然在这种情况下,我会收到错误消息,但是当我在这样的组件中添加布尔标志时:
app.component.html-> <div *ngIf='show'>test</div>
app.component.ts->
this.appState.subscribe((state: AppState) => { this.show= state.show; })

错误消失了,为什么我当然可以理解,但是我想知道您的意见,这是一个好的解决方案吗? 有什么优点和缺点??

不使用ngrx,但是使用querySelectorAll let chips = [].map.call(document.querySelectorAll('li.chip'), this.chipVisible);进行简单检查时遇到问题

@bogdancar的带有ChangeDetectorRef.detectChanges()的解决方案可以抑制该错误,但是最好不必通过首先修复引起该错误的内容来导入ChangeDetectorRef。

在angular 4.4.4中使用rxjs时遇到相同的问题。 我只是订阅由DI服务提供的可观察对象。 我的工作如下:

this.eventBroadcastService.getCustomEvent({type: 'loginOpen'}).subscribe(e => { setTimeout(() => { this.isBlur = true; }, 0); });

我使用setTimeout解决。 现在可以了,但是很棘手。 我感觉不好

ngOnChanges(changes:SimpleChanges | any)可以解决问题

只需在ngDoCheck()方法内移动动态更改参数逻辑后即可解决此问题。
ngDoCheck(): void { this.checkValid = this.sourceArr.includes((this.chanedVal).toUpperCase()); }

@ asrinivas61我认为这是一个非常糟糕的主意。 Angular经常调用ngDoCheck ,如果您的sourceArr有很多数据,可能会损害您的性能。

参见https://angular.io/guide/lifecycle-hooks#other -angular-lifecycle-hooks

我宁愿建议现在将ngAfterViewInitsetTimeout

setTimeout(_ => /* your code here */, 1000);

例:

  public ngAfterViewInit(): void {
    // We use setTimeout to avoid the `ExpressionChangedAfterItHasBeenCheckedError`
    // See: https://github.com/angular/angular/issues/6005
    setTimeout(_ => this.isLoadingInProgress = false, 1000);
  }

@dgroh不需要添加实际的超时,您只需要js在下一个异步任务中执行代码,这意味着需要运行另一个更改检测。

@victornoel我坚信,除非您显式调用ChangeDetectorRef detectChanges ,否则我认为这将适用于可观察对象。 我希望不需要所有这些。

@dgroh您一定误会了我,我只是提倡写作:

public ngAfterViewInit(): void {
    // We use setTimeout to avoid the `ExpressionChangedAfterItHasBeenCheckedError`
    // See: https://github.com/angular/angular/issues/6005
    setTimeout(_ => this.isLoadingInProgress = false);
  }

是否不可能像setTimeout一样在ngAfterViewInit之后添加步骤? (例如: ngAfterViewLoad

@ aks4it ,这似乎可行,但我想了解更多有关它如何运作的细节? 动态组件生成代码是否同步?

来自angular docs的官方plunker演示具有相同的错误。 你能在那里修理吗? :) 谢谢

@luckylooke

  ngAfterViewInit() {
    //this.loadComponent();
    this.getAds();
  }

这有效吗?

我不理解您的要求,但您是否真的希望间隔3秒? 如果是的话,这是可行的,即使您想直接调用我成功的loadComponent:

ngAfterViewInit() {
    setTimeout(()=>{this.loadComponent()},0)
  }

@istiti我可以通过ChangeDetectorRef解决方法来解决它,将detectChanges()放在... data = data之后

但是由于它是官方文档提供的工具,所以我对这个问题的官方解决方案感到好奇。 没有超时或ChangeDetectorRef。

抱歉,我在第一篇文章中太简短了。我想指出问题的严重性,因为即使有角度的文档在代码中也是如此。

仅供参考,我不确定这到底是什么原因,但是当我在main.ts中启用prod模式时,错误消失了
`enableProdMode()

//如果(environment.production){

// enableProdMode();

//}`

“ @ angular / core”:“ ^ 4.2.4”
“ typescript”:“〜2.3.3”

@bakthaa ...这被认为是行为,因为仅在开发模式而不是Angular应用的生产模式下调用了每个CD周期之后调用的控件检查(组件@Inputs()值是否相同)。 因此,问题在生产模式下是隐藏的,但仍然存在...并且可能是生产模式下各种不同的不良或误导性副作用的来源。

我使用的是Angular 5,通过将ChangeDetectorRef注入组件并强制其检测更改,可以解决此问题。

    constructor(private changeDetector: ChangeDetectorRef) {}

    ngAfterViewChecked(){
      this.changeDetector.detectChanges();
    }

@ Sabri-Bouchlema ...是的,这是一种方法...但是您仍然应该重新考虑一下...为什么在一个CD循环中更改组件@Inputs ...并确定是否逻辑是正确的...的确,在95%的情况下,可以更改代码以避免出现问题,而无需调用其他detectChanges()

使用角度5.1.2。 在ngAfterViewChecked中打开材质对话框时出现此错误。 正如@ aks4it所报告的那样,我必须将代码包装到setTimeout中才能正常工作。

我在4.01版中遇到同样的问题
ng-version =“ 4.0.1

同样在这里与角度4.4.6

使用this.changeDetector.detectChanges();修复

我已经使用模板变量编写了这个插件,但我不明白为什么会有ExpressionChangedAfterItHaHasBeenCheckedError。 如果我更改组件的顺序,则会出现错误提示:

https://plnkr.co/edit/Mc0UkTR2loI7wgmLAWtX

我已经尝试过changeDetector.detectChanges但没有用

@AlexCenteno ...因为评估逻辑是从上到下...并且value comp1 value属性首先设置@Input() public value:String=null; ,然后使用的引用进行重新设置comp2.value ... <div comp1 [value]="comp2.value"></div> ,同时通过自己的@Input()设置为Hello @Input() ...都经过一个CD周期...因此反复检查如果子组件上的输入与开发模式下CD周期开始时的输入相同,则检测到comp1 value属性中的更改。

嗨@ mlc-mlapis谢谢您的时间。 所以有没有办法避免错误? 我猜这是我一旦调用enableProdMode便不用担心的事情?

@AlexCenteno ...是的,在产品模式下,不会调用CD循环后的检查...但这并不意味着您应该对此感到满意。 您应该应用传递value交叉子组件的不同逻辑。 还有一个问题<div>没有属性value 。 为什么要使用属性组件? 为什么不只是:

<comp1 [value]="comp2.value"></comp1>
<comp2 #comp2 value="Hello"></comp2>

因为在<comp2> @Output()上使用[value]上更新comp1而没有任何错误。

[email protected]angular@5 standart store.select ExpressionChangedAfterItHasBeenCheckedError在开发人员模式下抛出警告

@Component({
  ...
  template `
  ...
      <div [ngClass]="{
        'home__sidenav': true,
        'home__sidenav--active': (isActiveSidenavMenu$ | async)
      }">
  ...
  `
})
export class HomeContainer {
  isActiveSidenavMenu$ = this.store.select(fromHome.isActiveSidenavMenu);
  ...
}

这种方法可以删除警告消息,但它似乎更复杂:

@Component({
  ...
  template `
  ...
      <div [ngClass]="{
        'home__sidenav': true,
        'home__sidenav--active': isActiveSidenavMenu
      }">
  ...
  `
})
export class HomeContainer {
  isActiveSidenavMenu$ = this.store.select(fromHome.isActiveSidenavMenu);
  isActiveSidenavMenu;

  ngOnInit() {
    this.isActiveSidenavMenu$.subscribe(res => {
      this.isActiveSidenavMenu = res;
      this.cd.detectChanges();
    });
  }
}

有什么想法可以更正确地解决这个问题吗?

@rimlin您是否尝试过直接订阅商店?

ngOnInit() {
  this.store.select(fromHome.isActiveSidenavMenu)
    .subsbrice(res => {
      this.isActiveSidenavMenu = res;
    });
}

我想我也有类似的问题。 并通过避免模板中的async解决了该问题。

@ piq9117感谢您的建议,尝试了您的解决方案,用更少的代码看起来更清晰,但是这里还需要调用this.cd.detectChanges(); ,否则视图将不会更新。

ngOnInit() {
    this.store.select(fromHome.isActiveSidenavMenu).subscribe(res => {
      this.isActiveSidenavMenu = res;
      this.cd.detectChanges();
    });
}

我正在使用Angular 5.2,我做了类似的事情。 为我工作

constructor(private changeDetector: ChangeDetectorRef) {}

ngAfterViewInit(){
  this.changeDetector.detectChanges();
}

使用Angular 5.2,使用detectChanges()进行修复

是的,我们也使用detectChanges()路线(角度5.1)修复了此问题。 我和其他许多人一样,尽管认为这仍然只是一种解决方法,从长远来看,不应将其视为答案。 应该有另一个可以使用的生命周期挂钩。

触发一个全新的变更检测周期以允许这样的变更似乎不是最佳的。

即使上面已经发布了所有答案和建议,这种情况似乎仍在继续。

  1. 解决此问题的正确方法是重新架构/重组组件和数据处理,以便从上到下使用一次更改检测传递。 这意味着没有任何子组件可以更改在父组件模板中使用的值。 重新评估所构建的内容,并验证数据仅沿一个方向流向组件树。 在某些情况下,这就像将代码从ngAfterViewInit移出到ngOnInit
  2. this.changeDetectorRef.detectChanges();是处理第一步问题的少于5%的情况的官方公认方法。

其他说明:

  1. ngAfterViewInit执行任何操作时要非常小心,因为更改DOM中使用的值可能会导致此错误。
  2. 使用任何CheckChecked生命周期挂钩时要非常小心,因为它们可能在每个变更检测周期中被调用。
  3. setTimeout可以解决此问题,但是上面的两个解决方案是首选。
  4. 如上所述,忽略开发模式下的此错误,并对生产模式“修复”该错误无效感到高兴。 这些检查不是在生产模式下进行的,但是您的生产模式代码仍然使更改未被发现,并导致视图和数据不同步。 在某些情况下,这可能是无害的,但如果忽略,可能会导致数据/视图损坏和更严重的问题。
  5. 数据流在Angular中并没有真正改变的一种方法,因此发布您在Angular X版本中遇到的情况并没有太大帮助。
  6. 添加更多生命周期挂钩或状态不是解决方案。 开发人员只需添加违反数据流向这些挂钩的一种方式的代码,并触发相同的错误。

最后,请记住,此错误并不表示Angular存在问题。 在这里出现此错误是为了让您知道您在代码中做错了什么,并且违反了使Angular快速高效的单向数据流系统。

上面的帖子和许多博客文章对此进行了更深入的介绍,值得一读,因为您应该了解这种数据流模型以及如何构建组件和在其中传递数据的方式。

@Splaktar锁定此问题(并删除我)以使上述注释突出起来会更好吗? 线程已经太长,大多数线程都在重复。 😃

@Splaktar您指出“这意味着子组件不能更改在父组件模板中使用的值”。

假设您有一个包含导航,页面标题等的容器组件,并且它加载了一个子组件的集合,每个子组件都可以加载自己的子组件。 加载到容器中的组件将根据数据而变化,并且它们加载的子组件将根据数据而变化。 这些子组件中的每个子组件都必须能够将其有关的信息传达回父容器,以便父容器也可以向用户显示适当的顶级Nav(也许是面包屑痕迹或类似的东西)。作为可以在标题中使用的信息,以在容器级别识别加载到视图中的内容。

在这种情况下,根据我的经验,这是一种非常通用的体系结构,我将利用一个公共状态服务,子组件将向其注册自己,并且父组件进行订阅,以便子代可以教育父代,而父代可以显示适当的信息。

这不是遵循自下而上的方法,因为父级将从服务中获取所有相关数据,并通过单向通信将子级组件所需的任何信息下推。

这似乎与您上面的建议背道而驰,对我来说,这是问题的根源。 当子组件向服务注册时,父组件将引发错误。

这不正确吗?

永远不要忘记,这里有一个已批准的错误:
https://github.com/angular/angular/issues/15634

@alignsoft ...您

@trotyl@Splaktar的解决方案并不涉及到我的简单的问题,在这里https://github.com/angular/angular/issues/17572#issuecomment -311589290或?

@ Martin-Wegner ...就您而言,将ngAfterViewInit更改ngOnInit钩子就足够了。

@alignsoft ,就像@ mlc-mlapis所说的,Stackblitz示例对于说明这种情况的正确体系结构将非常有帮助。 使用正确的服务集,可观察的对象,生命周期挂钩等,这很有可能。对于StackOverflow,这种特定情况也是一个不错的话题。

@Splaktar一旦我确定了当前截止日期,我将模拟一个演示一个简单案例的项目。

@ mlc-mlapis哇,谢谢,它有效:)

@alignsoft ,今天我正在创建一个行为与您所描述的完全相同的应用程序。 @ mlc-mlapis,您可以在stackblitz上找到简化的示例。 如果单击左侧导航栏中的“ CMS系统”,则会抛出ExpressionChangedAfterItHasBeenCheckedError异常。

在此示例中,admin.component充当父组件,负责三件事:

  • 显示左导航
  • 子组件可以用来显示自己的代码消息的顶部代码
  • 一个主要内容部分,可通过<router-outlet></router-outlet>显示子组件特定的数据。

父组件

<div class="ticker-container" *ngIf="tickedMessage !== ''">
  <h1>{{tickedMessage}}</h1>
  <button type="button" (click)="tickedMessage = ''">
    close
  </button>
</div>

<div class="side-nav">
  <h2>side nav</h2>
  <ul>
    <li><a routerLink="/">dashboard</a></li>
    <li><a routerLink="/apps/thirdpartycms">CMS System</a></li>
  </ul>
</div>
<div class="main-content-container">
  <router-outlet></router-outlet>
</div>
export class AdminComponent implements OnInit, OnDestroy {
  private _tickedMessageSubscription: Subscription;

  tickedMessage: string = '';

  constructor(
    private router: Router,
    private adminService: AdminService
  ) { }

  ngOnInit() {
    /* 
      On the side nav if you click on CMS System, this piece of code throws the exception: 
      "ERROR Error: ExpressionChangedAfterItHasBeenCheckedError: Expression has changed 
       after it was checked. Previous value: 'false'. Current value: 'true'.""
    */
    this._tickedMessageSubscription = this.adminService.tickedMessageAnnounced$.subscribe((tickedMessage: string) => {
      this.tickedMessage = tickedMessage;
    });
  }

  ngOnDestroy() {
    this._tickedMessageSubscription.unsubscribe();
  }
}

在研究使用<router-outlet></router-outlet>时如何将数据传递回父组件时,我读到如果使用@Output将数据发送回父组件,则router-outlet指令将最终结束处理事件。 由于这是一个自定义事件,所以router-outlet指令将不知道如何处理它。 所以我跳过了这种方法。 在angular.io上阅读有关共享服务的信息后,我写了自己的:

共享服务

import { Injectable } from '@angular/core';
import { Subject }    from 'rxjs/Subject';

@Injectable()
export class AdminService {

  private _tickedMessageAnnounced = new Subject<string>();

  tickedMessageAnnounced$ = this._tickedMessageAnnounced.asObservable();

  announceTickedMessage(tickedMessage: string) {
    this._tickedMessageAnnounced.next(tickedMessage);
  }
}

子组件之一称为this.adminService.announceTickedMessage(this._tickedMessage);

子组件

import { Observable } from 'rxjs/Observable';
import { Component, ViewChild, OnInit, Inject } from '@angular/core';
import { AdminService } from '../../../core/admin/admin.service';

@Component({
  selector: 'cms',
  templateUrl: './thirdparty-cms.component.html',
  styleUrls: ['./thirdparty-cms.component.scss'],
})
export class ThirdPartyCmsComponent implements OnInit {
  private _tickedMessage: string;
  constructor(private adminService: AdminService) { }

  ngOnInit(): void {
    this._tickedMessage = 'Please do not create an entry for a page URL that does not exist in the CMS system';

    this.adminService.announceTickedMessage(this._tickedMessage);
  }
}

父组件引发了ExpressionChangedAfterItHasBeenCheckedError异常,因为它尝试使用子组件发送的已标记消息值更新其公共属性之一:

父组件(引发异常)

export class AdminComponent implements OnInit, OnDestroy {
  private _tickedMessageSubscription: Subscription;

  tickedMessage: string = '';

  constructor(
    private router: Router,
    private adminService: AdminService
  ) { }

  ngOnInit() {
    /* 
      On the side nav if you click on CMS System, this piece of code throws the exception: 
      "ERROR Error: ExpressionChangedAfterItHasBeenCheckedError: Expression has changed 
       after it was checked. Previous value: 'false'. Current value: 'true'.""
    */
    this._tickedMessageSubscription = this.adminService.tickedMessageAnnounced$.subscribe((tickedMessage: string) => {
      this.tickedMessage = tickedMessage;
    });
  }

  ngOnDestroy() {
    this._tickedMessageSubscription.unsubscribe();
  }
}

固定ExpressionChangedAfterItHasBeenCheckedError是通过异步执行订阅回调并等待标记的消息:

父组件(具有异步等待)

export class AdminComponent implements OnInit, OnDestroy {
  private _tickedMessageSubscription: Subscription;

  tickedMessage: string = '';

  constructor(
    private router: Router,
    private adminService: AdminService
  ) { }

  ngOnInit() {
    /* 
      This clears the ExpressionChangedAfterItHasBeenCheckedError exception.
    */
    this._tickedMessageSubscription = this.adminService.tickedMessageAnnounced$.subscribe(async (tickedMessage: string) => {
      this.tickedMessage = await tickedMessage;
    });
  }

  ngOnDestroy() {
    this._tickedMessageSubscription.unsubscribe();
  }
}

来自WPF世界,似乎我们需要告诉UI线程等待可观察的订阅回调,因为可观察对象是异步执行的?

我只是从Angular开始,所以请对我的Angular skillz有所同情:P

@ktabarez谢谢,这对我帮助很大!

@ktabarez谢谢! 看来效果很好! 我在超时中将订阅包装在父组件中,该超时也起作用,但感觉很脏。

我看到,当查询redux状态时,它保证会传递,但是在启动时像放在ngOnInit而不是构造函数中一样

@ktabarez非常感谢!

您的async ... await in subscribe解决方案有效。

@ktabarez您的解决方案在本质上与setTimeout({},0)相同,但是感觉更优雅。 很喜欢! 这也让我意识到可以等待任何事情(非常类似于C#等待Task.FromResult(tickedMessage)),为此加油!

所以,问题是:为什么我们必须对可观察对象的属性计算进行异步/等待,该属性是从后端获取可能经常更改的数据? ngClass怎么办呢? 我在使用{observable | 异步}管道。 因此,异步/等待不是我的最佳选择。 (或者异步管道不是最好的方法)

我的父级组件有多个子级组件时出现此问题。 子组件之一具有一个Input ,它需要来自父组件属性的数据,并且该属性从Observable / Promise(都尝试)获取数据。

parent.component.html

<mat-tab-group>
   <mat-tab>
      <app-child-1></app-child-1>
   </mat-tab>
   <mat-tab>
      <app-child-2></app-child-2>
   </mat-tab>
   <mat-tab>
      <app-child-3 [datasource]="data"></app-child-3>
   </mat-tab>
</mat-tab-group>

parent.component.ts

// ... omitted...
data: any;

ngOnInit() {
   this.service1.getMethod(some_id).then(data => this.data = data);
}

我遇到了该线程中找到的每个解决方案。 但对我而言,没有任何效果。 幸运的是,有人暗示这是由于操作DOM而引起的问题,例如将表达式放在_(ngIf)_上。

因此,为了避免这种情况,我尝试尝试使用ng-templateng-containerngTemplateOutlet

<ng-container *ngTemplateOutlet="data ? content : loading"></ng-container>

<ng-template #content>
   <mat-tab-group>
      <mat-tab>
         <app-child-1></app-child-1>
      </mat-tab>
      <mat-tab>
         <app-child-2></app-child-2>
      </mat-tab>
      <mat-tab>
         <app-child-3 [datasource]="data"></app-child-3>
      </mat-tab>
   </mat-tab-group>
</ng-template>

<ng-template #loading>Loading your component. Please wait</ng-template>

我无需使用ChangeDetectorRefsetTimeout()就能解决此问题

对我来说,根据条件显示/隐藏字段时发生此错误,我可以在应用字段设置的值后解决它:

this.form.get ('field').updateValueAndValidity();

我一直在尝试使用在init上打开的mat-dialog来做到这一点,而且我确实尝试了在该线程上看到的所有解决方案(设置超时,承诺解决,可观察的管道,view init之后,content init之后)以及其中的组合。 仍然不起作用。 然后,我看到了@Splaktar帖子,内容涉及如何违反数据流的一种方式,因此我在ngrx中做了一个标志,说要打开对话框并订阅该标志,这在最初是错误的,但仍然出现此错误。 我到底该怎么办? 如果不将模式对话框附加到一些完全独立的事件上,是否真的没有办法打开模式对话框?

在初始化中:
this.store.pipe( select(getShouldLogin), ).subscribe((shouldLogin: boolean) => { if (shouldLogin) { this.openLoginDialog(); } }); this.checkUserId();

辅助功能:
checkUserId() { const id = this.cookies.getUserId(); if (!id || id === 'undefined') { this.connApi.setShouldLogin(true); } }
openLoginDialog() { const dialogRef = this.dialog.open(LoginDialogComponent, { width: '400px', height: '300px', }); dialogRef.afterClosed().subscribe(result => this.handleLogin(result)); }

使用角度6.0.0-rc.1

我在ngrx中使用angular并在提交表单后收到此错误。
设定固定
ChangeDetectionStrategy.OnPush
表单组件

仍然有问题。 尝试了各种解决方法。 setTimeout第一次起作用,但是随后触发dialog.open导致相同的错误。 有什么状态吗?

我正面临着同样的问题。 尝试了所有方法,但似乎此问题是由于数据流的一种方式引起的。
在这里,我正在尝试更新selectedwoids。 解决问题的地方,但出现相同的错误。

        <p-row> <p-column class="text-left" footer="{{selectedWoids.length}} {{getWoidString(selectedWoids.length)}} selected out of {{getTotalCounts()}} {{getWoidString(getTotalCounts())}}" colspan="11"></p-column> </p-row>

私人setSelectedWoidsCount(){
if(this.isSelectAllWoids){
this.selectedWoids = this.allWoids;
}
}

因为您打破了Angular的渲染流程...

4.1.2切换到4.2.3之后,我看到类似的“ ExpressionChanged ...”错误。

就我而言,我有两个通过服务交互的组件。 该服务是通过我的主模块提供的,并且ComponentAComponentB之前创建。

4.1.2 ,以下语法可以正常工作而不会出现错误:

_ComponentA模板:_

<ul *ngIf="myService.showList">
...

_ComponentB类别:_

ngOnInit() {
  this.myService.showList = false; //starts as true in the service
  ...
}

4.2.3 ,我必须按如下所示修改ComponentA的模板,以避免出现“ ExpressionChanged ...”错误:

_ComponentA模板:_

<ul [hidden]="!myService.showList">
...

我仍在尝试弄清楚为什么这只是一个问题,为什么从*ngIf切换到[hidden]起作用。

从ngIf切换到[hidden]时,您是否发现它是如何工作的?

Angular 6中是否有针对此问题的解决方案?

@alokkarma ...太旧了...您至少应该将其迁移到Angular 6或5。 但是您的示例代码看起来很清楚……您正在更改子组件B中的service属性,这会影响子组件A中的某些行为。两个子组件都放置在同一父组件中,因此这三个子组件在一个CD周期的关系。 你能指望什么?

同一案例在4.1.2版本中起作用的事实与某些错误有关,这些错误已在更高的版本(包括4.2.3)中消除。

我正在使用Angular6。我正在使用MessageService在身份验证组件和应用程序组件之间进行通信,以在用户登录(或未登录)时显示不同的导航栏内容

除此错误外,它工作正常!
我已经尝试过其他解决方案,例如EventEmitter,但是它不起作用! 或我得到相同的错误。

@dotNetAthlete ...在Stackblitz上显示了一个简单的复制演示。 没有真实的代码就很难谈论它。

@dotNetAthlete我使用相同的基本机制,其中有一个状态服务,该状态服务在主容器组件中包含作为行为主题的页面标题(asObservable),而子组件则加载到其中,以在状态服务中设置适当的页面标题供父容器观察和显示。

当容器初始化该值,然后子组件立即更新它时,总会出现“表达式更改”错误,因此,我现在在父/容器中执行此操作:

    this.stateSvc.currentPageTitle$
      .subscribe(
        (async (pageTitle) => {
          this.pageTitle = await pageTitle;
        }))

国家服务具有以下功能:

  // Store
  private currentPageTitleStore = new BehaviorSubject<string>("");

  // Getter (as Observable)
  public currentPageTitle$ = this.currentPageTitleStore.asObservable();

  // Setter
  public setCurrentPageTitle(pageTitle: string) {
    this.currentPageTitleStore.next(pageTitle);
  }

这消除了问题。 此解决方案基于该线程中其他人员的一些有用反馈。

@alignsoft-漂亮的解决方案-在Angular 6应用中已修复的问题。

我现在在角度7的页面上重新看到此内容...
我正在为父母ngAfterViewInit设置子组件值
必须在子组件上执行此操作

  public activate() {
    setTimeout(() => this.active = true);
  }

我在此问题线程https://github.com/angular/angular/issues/10762中找到了解决方案
使用AfterContentInit生命周期挂钩后,错误消失了。

@alignsoft ,只是增加了您的更改,因为我们使用的是BehavioralSubject而不是Subject,

我将其细化如下:

\\ Component
export class AppComponent implements OnInit {
  isLoading: Boolean;

  constructor(private loaderService: LoaderService) { }

  ngOnInit(){
    this.loaderService.isLoading.subscribe(async data => {
      this.isLoading = await data;
    });
  }
}
\\ Service
@Injectable({
  providedIn: 'root'
})
export class LoaderService {
  // A BehaviorSubject is an Observable with a default value
  public isLoading: BehaviorSubject<Boolean> = new BehaviorSubject(false);
  constructor() {}
}
\\ Any other component or in my case, an interceptor that has the LoaderService injected
this.loaderService.isLoading.next(true);

但是我同意@daddyschmack ,我想获取任何指向与withotu有关的上述修复方法( cdr.detectionChanges()async awaitaftercontentchecked )的信息的资源,为什么[hidden]属性有效,而不是*ngIf

@acidghost谢谢您的链接,它解决了我的问题。 实际上,我已经放弃尝试在应用程序的一个区域中对其进行修复,但是当它再次开始在新区域中发生时...

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

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

_此动作已由漫游器自动执行。_

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