Angular 已解释的表达式更改终端检查错误

Angular 已解释的表达式更改终端检查错误,angular,angular2-changedetection,angular2-databinding,Angular,Angular2 Changedetection,Angular2 Databinding,请向我解释为什么一直出现此错误:expressionChangedTerithasBeenCheckedError:Expression在检查后已更改。 显然,我只在开发模式下得到它,它不会发生在我的生产构建中,但这非常烦人,我根本不理解在开发环境中出现错误的好处,而这些错误不会出现在prod上——可能是因为我缺乏理解 通常,修复非常简单,我只是将导致错误的代码包装在setTimeout中,如下所示: *ngFor="let x of myArray?.splice(0, 10)&qu

请向我解释为什么一直出现此错误:
expressionChangedTerithasBeenCheckedError:Expression在检查后已更改。

显然,我只在开发模式下得到它,它不会发生在我的生产构建中,但这非常烦人,我根本不理解在开发环境中出现错误的好处,而这些错误不会出现在prod上——可能是因为我缺乏理解

通常,修复非常简单,我只是将导致错误的代码包装在setTimeout中,如下所示:

*ngFor="let x of myArray?.splice(0, 10)"
setTimeout(()=>{
this.isLoading=true;
}, 0);
或者使用如下构造函数强制检测更改:
构造函数(私有cd:ChangeDetectorRef){}

this.isLoading=true;
this.cd.detectChanges();

但是为什么我经常会遇到这种错误呢?我想了解它,以便在将来避免这些黑客修复。

此错误表明应用程序中存在实际问题,因此抛出异常是有意义的

devMode
change detection中,在每次常规的更改检测运行后,都会添加一个额外的回合,以检查模型是否已更改

如果模型在常规和附加变化检测回合之间发生变化,这表明

  • 更改检测本身导致了更改
  • 方法或getter每次调用时都返回不同的值
这两者都不好,因为不清楚如何继续,因为模型可能永远不会稳定

如果Angular运行更改检测直到模型稳定,它可能会永远运行。 如果Angular未运行更改检测,则视图可能不会反映模型的当前状态


另请参见更新

我强烈建议从第一步开始:正确地考虑在
构造函数中可以做什么,而不是在
ngOnChanges()中应该做什么

原创

这与其说是一个答案,不如说是一个旁注,但它可能会对某些人有所帮助。当我试图使按钮的存在取决于窗体的状态时,偶然发现了这个问题:

Yo
据我所知,这种语法导致根据条件在DOM中添加和删除按钮。这反过来会导致
表达式更改TerithasBeenCheckedError

在我的案例中,修复方法(尽管我并不声称完全理解差异的含义),是使用
display:none

Yo

我也有类似的问题。查看后,我将
ngAfterViewInit
更改为
ngAfterContentInit
,它成功了。

我在Ionic3中遇到了这种错误(它使用Angular 4作为其技术堆栈的一部分)

对我来说,它是这样做的:

因此,我试图根据屏幕操作的模式,有条件地将a的类型从
pin
更改为
删除圆


我猜我必须添加一个
*ngIf

我的组件中的一个数组中的值发生变化时,我遇到了相同的问题。但是,我没有检测值变化的变化,而是将组件变化检测策略更改为
onPush
(它将检测对象变化而不是值变化的变化)


一旦我了解了它们与变化检测的关系,我就有了很多了解

我试图使用Angular来更新绑定到元素的
*ngIf
的全局标志,并试图在另一个组件的
ngOnInit()
生命周期钩子中更改该标志

根据文档,在Angular检测到更改后调用此方法:

在第一个ngOnChanges()之后调用一次

因此,更新
ngochanges()
中的标志不会启动更改检测。然后,一旦更改检测再次自然触发,则标志的值已更改并抛出错误

就我而言,我改变了这一点:

constructor(private globalEventsService: GlobalEventsService) {

}

ngOnInit() {
    this.globalEventsService.showCheckoutHeader = true;
}
为此:

constructor(private globalEventsService: GlobalEventsService) {
    this.globalEventsService.showCheckoutHeader = true;
}

ngOnInit() {

}

它解决了这个问题:)

在我的例子中,我在运行测试时在规范文件中遇到了这个问题

我不得不将ngIf
更改为
[hidden]



对于我的问题,我阅读了“ExpressionChangedTerithasbeen在afterViewInit中更改组件“非模型”值时检查错误”,并决定添加ngModel

<input type="hidden" ngModel #clientName />


它解决了我的问题,我希望它能帮助别人。

按照以下步骤操作:

一,。 通过从@angular/core导入“ChangeDetectorRef”,使用“ChangeDetectorRef”,如下所示:

import{ ChangeDetectorRef } from '@angular/core';
constructor(   private cdRef : ChangeDetectorRef  ) {}
二,。 在构造函数()中实现它,如下所示:

import{ ChangeDetectorRef } from '@angular/core';
constructor(   private cdRef : ChangeDetectorRef  ) {}
三,。 将以下方法添加到您正在调用的函数中,该函数是在单击按钮之类的事件中调用的。看起来是这样的:

functionName() {   
    yourCode;  
    //add this line to get rid of the error  
    this.cdRef.detectChanges();     
}

以下是我对正在发生的事情的看法。我没有阅读文档,但我确信这是显示错误的部分原因

*ngIf="isProcessing()" 
使用*ngIf时,它会通过每次条件更改时添加或删除元素来物理更改DOM。因此,如果条件在渲染到视图之前发生更改(这在Angular的世界中非常可能),则会抛出错误。参见开发和生产模式之间的说明

[hidden]="isProcessing()"
使用
[hidden]
时,它不会实际更改
DOM
,而只是从视图中隐藏
元素,很可能是在后面使用
CSS
。元素仍在DOM中,但不可见,具体取决于条件的值。这就是为什么在引用文章时使用
[hidden]

时不会发生错误的原因

因此,变更检测背后的机制实际上是以一种同步执行变更检测和验证摘要的方式工作的。这意味着,如果我们异步更新属性,这些值将不会
// image-carousel.component.ts
@HostBinding('style.background') 
style_groupBG: string;
@Input('carouselConfig')
public set carouselConfig(carouselConfig: string) 
{
    this.style_groupBG = carouselConfig.bgColor;   
}
@ViewChild(ImageCarousel) carousel: ImageCarousel;

ngAfterViewInit()
{
    this.carousel.carouselConfig = { bgColor: 'red' };
}
ngAfterViewInit()
{
    this.carousel.carouselConfig = { ... };
    this.cdr.detectChanges();
}
    console.log('EXPRESSIONCHANGED - HomePageComponent: constructor');
    console.log('EXPRESSIONCHANGED - HomePageComponent: setting config', newConfig);
    console.log('EXPRESSIONCHANGED - HomePageComponent: setting config ok');
    console.log('EXPRESSIONCHANGED - HomePageComponent: running detectchanges');
    console.log('EXPRESSIONCHANGED - ChildComponent: setting config');
    console.log('EXPRESSIONCHANGED - ChildComponent: setting config ok');
    console.log('EXPRESSIONCHANGED - ChildComponent: running detectchanges');
    this.cdr.detectChanges();
<div>{{changeMyModelValue()}}</div> <!--don't do this!  or you could get error: ExpressionChangedAfterItHasBeenCheckedError-->
....
<div *ngIf="true">{{myModel.value}}</div>
ngAfterViewChecked(){
  this.changeDetector.detectChanges();
}
import { ChangeDetectorRef, AfterContentChecked} from '@angular/core';

  constructor(
  private cdref: ChangeDetectorRef) { }

  ngAfterContentChecked() {

    this.cdref.detectChanges();

  }
    <h1 [hidden]="!(loaderService.isLoading | async)">
        THIS WORKS FINE
        (Loading Data)
    </h1>

    <h1 *ngIf="!(loaderService.isLoading | async)">
        THIS THROWS ERROR
        (Loading Data)
    </h1>
  displayMain: boolean;
  ngOnInit() {
    this.displayMain = false;
    // Service Calls go here
    // Service Call 1
    // Service Call 2
    // ...
    this.displayMain = true;
  }
<div *ngIf="displayMain"> <!-- This is the Root Element -->
 <!-- All the HTML Goes here -->
</div>
constructor(
    private ngZone: NgZone,
    private changeDetectorRef: ChangeDetectorRef,
) {}

ngOnInit() {
    this.ngZone.runOutsideAngular(() => {
        this.appService.appLoader$.subscribe(value => {
            this.loading = value;
            this.changeDetectorRef.detectChanges();
        });
    });
}
setTimeout(() => {
  this.modalReference = this.modalService.open(this.modal, { size: "lg" });
});
*ngFor="let x of myArray?.splice(0, 10)"
*ngFor="let x of (myArray$ | async)?.splice(0, 10)"