ngIf - 检查后表达式已更改

ngIf - Expression has changed after it was checked

我有一个简单的方案,但就是无法正常工作!

在我看来,我在一个高度有限的框中显示了一些文本。

正在从服务器获取文本,因此当文本进入时视图会更新。

现在我有一个 'expand' 按钮,它有一个 ngIf 如果框中的文本溢出, 应该 显示该按钮。

问题是因为文本在获取时发生变化,'expand' 按钮的状态在 Angular 的变化检测完成后变为 true...

所以我得到这个错误:检查后表达式已更改。先前值:'false'。当前值:'true'.

很明显按钮没有显示...

see this Plunker(检查控制台以查看错误...)

知道如何进行这项工作吗?

出现此错误是因为您在 dev mode:

dev mode中,变化检测在每次常规变化检测后增加一个额外的回合运行检查模型是否发生变化。

因此,要强制更改检测 运行 下一次报价,我们可以这样做:

export class App implements AfterViewChecked {

  show = false; // add one more property

  constructor(private cdRef : ChangeDetectorRef) { // add ChangeDetectorRef
    //...
  }
  //...
  ngAfterViewChecked() {
    let show = this.isShowExpand();
    if (show != this.show) { // check if it change, tell CD update view
      this.show = show;
      this.cdRef.detectChanges();
    }
  }

  isShowExpand()
  {
    //...
  }
}

现场演示:https://plnkr.co/edit/UDMNhnGt3Slg8g5yeSNO?p=preview

出于某种原因,@Tiep Phan 的回答对我来说无法强制更改检测,但使用 setTimeout(它也强制更改检测)可以。

我也只需要将它添加到有问题的行,并且它与我在 ngOnInit 中已有的代码一起工作正常,而不必添加 ngAfterViewInit。

示例:

ngOnInit() {
    setTimeout(() => this.loadingService.loading = true);
    asyncFunctionCall().then(res => {
        this.loadingService.loading = false;
    })
}

此处有更多详细信息:https://github.com/angular/angular/issues/6005

在 ngAfterContentChecked 解决了我的问题后,导致更改检测器 运行

示例如下:

import { ChangeDetectorRef,AfterContentChecked} from '@angular/core'
export class example implements OnInit, AfterContentChecked {
    ngAfterContentChecked() : void {
        this.changeDetector.detectChanges();
    }
}

虽然,当我阅读一些文章时,这个问题在生产模式下得到了解决,没有任何必要的修复。

以下是出现此类问题的可能原因:

它强制执行单向数据流:当我们的控制器 类 上的数据更新时,更改检测 运行 并更新视图。

但是视图的更新本身不会触发进一步的更改,而这些更改又会触发对视图的进一步更新

https://blog.angular-university.io/how-does-angular-2-change-detection-really-work/

如果您使用 BehaviorSubject 在组件之间共享值:

component.ts:

import { Observable } from 'rxjs/Observable';
import {tap, map, delay} from 'rxjs/operators';

private _user = new BehaviorSubject<any>(null);
user$ = this._user.asObservable();

Observable.of('response').pipe(
            tap(() =>this._user.next('yourValue'),
            delay(0),
            map(() => 'result')
          );

component.html:

<login *ngIf="!(dataService.user$ | async); else mainComponent"></login>

为了克服这个问题,您可以移动改变 *ngIf 状态的变量,从 ngAfterViewInitngOnInit constructor.因为在调用AfterViewInit方法时不允许改变html的状态。

正如@tiep-phan 所说,另一种方法是将 ChangeDetectorRef 传递给构造函数并在 AfterViewInit 方法中更改 *ngIf 的状态后调用 this.chRef.detectChanges()。

对于遇到类似问题的任何人,基本上您是在尝试在不应该更新的地方更新 dom,在此 link https://blog.angular-university.io/angular-debugging/ 中您可以找到详细信息如何调试,找到产生问题的确切代码片段,以及关于如何修复它的一些想法

我们还可以通过将 changeDetection 更改为 OnPush 来抑制抛出此 ExpressionChangedAfterItHasBeenCheckedError。因此,不会执行额外的更改检测,因此不会抛出错误。 为此,您需要在 .ts 文件中添加 ChangeDetectionStrategy.OnPush 作为 @Component 装饰器的一部分,如下所示:

@Component({
  selector: 'your-component',
  templateUrl: 'your-component.component.html',
  changeDetection: ChangeDetectionStrategy.OnPush
})

我收到此错误是因为我在加载时打开了一个 Ngbmodal 弹出窗口,其中包括在检查后更改的对象。我通过调用 setTimeout 中的模态打开函数解决了这个问题。

setTimeout(() => {
  this.modalReference = this.modalService.open(this.modal, { size: "lg" });
});

Angular 变更检测过程是同步的,为避免此错误,我们可以将其作为异步过程,并且我们可以通过执行类似的操作使该代码在变更检测之外 运行。 我们可以将此代码段放在 onInit 或 Constructor 方法中。

this.ngZone.runOutsideAngular(() => {    
     this.interval = window.setInterval(() => {        

         //logic which we want to handle

     }, 1)});

实施AfterContentChecked方法。

constructor(
    private cdr: ChangeDetectorRef,
) {}

ngAfterContentChecked(): void {
   this.cdr.detectChanges();
}