【问题标题】:ExpressionChangedAfterItHasBeenCheckedError: Expression has changed after it was checked. Previous value: 'undefined'ExpressionChangedAfterItHasBeenCheckedError:表达式在检查后已更改。以前的值:'未定义'
【发布时间】:2018-01-10 02:16:41
【问题描述】:

我知道堆栈溢出中已经发布了很多相同的问题,并尝试了不同的解决方案来避免运行时错误,但没有一个对我有用。

组件和 Html 代码

export class TestComponent implements OnInit, AfterContentChecked {
    @Input() DataContext: any;
    @Input() Position: any;
    sampleViewModel: ISampleViewModel = { DataContext: : null, Position: null };
    constructor(private validationService: IValidationService, private modalService: NgbModal, private cdRef: ChangeDetectorRef) {
    }

    ngOnInit() {

    }
    ngAfterContentChecked() {

            debugger;
            this.sampleViewModel.DataContext = this.DataContext;
            this.sampleViewModel.Position = this.Position;

    }


<div class="container-fluid sample-wrapper text-center" [ngClass]="sampleViewModel.DataContext?.Style?.CustomCssClass +' samplewidget-'+ sampleViewModel.Position?.Columns + 'x' + sampleViewModel.Position?.Rows">
     //some other html here
</div>

请注意:此组件是使用 DynamicComponentLoader 动态加载的

在解决问题后,我发现了几个问题

首先,这个子组件是通过使用 DynamicComponentResolver 动态加载并传递如下输入值

 ngAfterViewInit() {
    this.renderWidgetInsideWidgetContainer();

  }


  renderWidgetInsideWidgetContainer() {
    let component = this.storeFactory.getWidgetComponent(this.dataSource.ComponentName);
    let componentFactory = this._componentFactoryResolver.resolveComponentFactory(component);
    let viewContainerRef = this.widgetHost.viewContainerRef;
    viewContainerRef.clear();
    let componentRef = viewContainerRef.createComponent(componentFactory);
    debugger;
    (<IDataBind>componentRef.instance).WidgetDataContext = this.dataSource.DataContext;
    (<IDataBind>componentRef.instance).WidgetPosition = this.dataSource.Position;

  }

即使我像下面这样更改了我的子组件 html,我也会遇到同样的错误。只需添加一个角度 ngclass 属性

<div class="container-fluid ds-iconwidget-wrapper text-center" [ngClass]="Sample">

</div>

我的数据绑定和一切工作正常。我需要对父组件做任何事情吗? 我已经尝试过子组件中的所有生命周期事件

【问题讨论】:

  • 如何添加TestComponent
  • 首先将this.renderWidgetInsideWidgetContainer();ngAfterViewInit移动到ngOnInit
  • @Maximus 感谢您的时间..您的文章很棒
  • @JEMI,不客气)我有很多有趣的文章,请检查
  • @Maximus 当然。我现在已经是你的追随者了。你的每一篇文章都很有深度。

标签: angular runtime-error


【解决方案1】:

RxJS 定时器()

像其他人建议的那样,您可以通过将代码放在 setTimeout(..) 函数中来解决问题。它本质上会将执行移到事件循环堆栈之外,从而进入 Angular 的下一个变更检测周期。

RxJS 的 own timeout implementation 称为 timer() - 它接受毫秒作为参数并返回一个 observable。由于我们只需要在事件循环堆栈清理干净(并且 Angular 已完成所有渲染计算)之后执行,我们可以使用 timer() 不带任何参数(无论如何默认为 0):

ngAfterViewInit() {
    timer().subscribe(() => {
        // your code here
    })
}

JS 事件循环架构:

【讨论】:

    【解决方案2】:

    *NgIf 在这里会产生问题,所以要么使用 display none CSS,要么更简单的方法是使用 [hidden]="!condition"

    【讨论】:

    • 太棒了!最好和最简单的方法!
    【解决方案3】:

    这段代码解决了我的问题。

    import {ChangeDetectorRef } from '@angular/core';
    
    constructor( private cdref: ChangeDetectorRef ) {}
    
    ngAfterContentChecked() {
      this.cdref.detectChanges();
    }
    

    【讨论】:

      【解决方案4】:

      我遇到了麻烦。

      错误:ExpressionChangedAfterItHasBeenCheckedError:表达式在检查后已更改。 'mat-checkbox-checked' 的先前值:'true'。当前值:'false'。

      这里的问题是直到下一个更改检测周期运行时才检测到更新的值。

      最简单的解决方案是添加变更检测策略。 将这些行添加到您的代码中:

      import { ChangeDetectionStrategy } from "@angular/core";  // import
      
      @Component({
        changeDetection: ChangeDetectionStrategy.OnPush,
        selector: "abc",
        templateUrl: "./abc.html",
        styleUrls: ["./abc.css"],
      })
      

      【讨论】:

      【解决方案5】:

      试试这个,在 ngOnInit()

      中调用你的代码
      someMethod() // emitted method call from output
      {
          // Your code 
      }
      
      ngOnInit(){
        someMethod(); // call here your error will be gone
      }
      

      【讨论】:

        【解决方案6】:
         ngAfterViewInit() {
           setTimeout(() => {
             this.renderWidgetInsideWidgetContainer();
           }, 0);
          }
        
        

        这是解决这个问题的好方法。

        【讨论】:

        • 其他解决方案都不适用于我的特定深奥案例,所以几个小时后我放弃了,不得不求助于这个,它奏效了。
        • 我不明白它是如何工作的,但它也对我有用。
        • @FatihErsoy 您可以将 setTimeout() 视为在整个 JS 事件队列的末尾放置一些东西。因此,在这种情况下,即使 Timeout 为“0”,它也会将要运行的代码放在要运行的事件的末尾,这意味着 Timeout 中的代码可能会在队列中更靠前的 Angular 渲染代码之后运行。归根结底,setTimeout() 感觉像是一个 hack 解决方案,但是当 Angular 生命周期的行为不符合您的预期并且您迫切希望让它工作时,您可以使用它。
        • 我在加载带有复选框列表的对话框组件时遇到了这个问题。通过将我的代码包装到 setTimeout() 函数中,对话框显示的延迟时间足以消除该错误。
        【解决方案7】:
        setTimeout(() => { // your code here }, 0);
        

        我将我的代码封装在 setTimeout 中并且它起作用了

        【讨论】:

          【解决方案8】:

          两种解决方案:

          1. 确保您有一些绑定变量,然后将该代码移至 settimeout( { }, 0);
          2. 将相关代码移至 ngAfterViewInit 方法

          【讨论】:

          • 这取决于它将您的代码移动到事件队列,并且它将在当前执行完成时执行。 JavaScript 是一种单线程脚本语言,因此它可以一次执行一段代码(由于其单线程性质),这些代码块中的每一个都“阻塞”了其他异步事件的进程。 settimeout 代码只会在您当前的代码执行完毕后才会执行。
          • 嗯..这个固定的地雷。我去了组件并将对绑定的所有更改都设置为 0 秒的超时,并且它可以工作。这是一个好的解决方案还是一个黑客?我正在调用在生命周期中可能会更改绑定值 2 次的方法。
          【解决方案9】:

          如果您将&lt;ng-content&gt;*ngIf 一起使用,您一定会陷入此循环。

          我发现的唯一出路是将*ngIf 更改为display:none 功能

          【讨论】:

          • 或者可以使用[hidden]="!condition"
          【解决方案10】:

          我在尝试做和你一样的事情时遇到了同样的问题,我用类似于 Richie Fredicson 的答案解决了这个问题。

          当您运行 createComponent() 时,它会使用未定义的输入变量创建。 然后,当您将数据分配给这些输入变量时,它会改变事物并在您的子模板中导致该错误(在我的情况下,这是因为我在 ngIf 中使用了输入,一旦我分配了输入数据,它就会改变)。

          在这种特定情况下,我能找到避免它的唯一方法是在分配数据后强制进行更改检测,但是我没有在 ngAfterContentChecked() 中这样做。

          您的示例代码有点难以理解,但如果我的解决方案适合您,它会是这样的(在父组件中):

          export class ParentComponent implements AfterViewInit {
            // I'm assuming you have a WidgetDirective.
            @ViewChild(WidgetDirective) widgetHost: WidgetDirective;
          
            constructor(
              private componentFactoryResolver: ComponentFactoryResolver,
              private changeDetector: ChangeDetectorRef
            ) {}
          
            ngAfterViewInit() {
              renderWidgetInsideWidgetContainer();
            }
          
            renderWidgetInsideWidgetContainer() {
              let component = this.storeFactory.getWidgetComponent(this.dataSource.ComponentName);
              let componentFactory = this.componentFactoryResolver.resolveComponentFactory(component);
              let viewContainerRef = this.widgetHost.viewContainerRef;
              viewContainerRef.clear();
              let componentRef = viewContainerRef.createComponent(componentFactory);
              debugger;
              // This <IDataBind> type you are using here needs to be changed to be the component
              // type you used for the call to resolveComponentFactory() above (if it isn't already).
              // It tells it that this component instance if of that type and then it knows
              // that WidgetDataContext and WidgetPosition are @Inputs for it.
              (<IDataBind>componentRef.instance).WidgetDataContext = this.dataSource.DataContext;
              (<IDataBind>componentRef.instance).WidgetPosition = this.dataSource.Position;
              this.changeDetector.detectChanges();
            }
          }
          

          除了我使用@ViewChildren 而不是@ViewChild 因为我有多个宿主元素之外,我的几乎相同。

          【讨论】:

            【解决方案11】:

            你必须告诉angular你在ngAfterContentChecked之后更新了内容 您可以从@angular/core 导入ChangeDetectorRef 并调用detectChanges

            import {ChangeDetectorRef } from '@angular/core';
            
            constructor( private cdref: ChangeDetectorRef ) {}
            
            
            ngAfterContentChecked() {
            
            this.sampleViewModel.DataContext = this.DataContext;
            this.sampleViewModel.Position = this.Position;
            this.cdref.detectChanges();
            
             }
            

            【讨论】:

            • 查看有问题的 cmets 我已经尝试过这个解决方案,但仍然遇到同样的错误
            • 我导入了只有 this.cdref.detectChanges(); 的 AfterContentChecked这对我有用,谢谢
            • @MohamedAboElmagd 你能发布一下你是如何做到这一点的吗?
            • 使用 this.cdref.detectChanges();修改后
            • 当我打开下拉菜单和其他项目时,它可能会被调用。
            【解决方案12】:

            当子组件/指令的绑定更新已经完成时,会触发ngAfterContentChecked 生命周期挂钩。但是您正在更新用作ngClass 指令的绑定输入的属性。那就是问题所在。当 Angular 运行验证阶段时,它会检测到属性有待更新并抛出错误。

            要更好地理解错误,请阅读以下两篇文章:

            想想为什么需要更改ngAfterViewInit 生命周期挂钩中的属性。在ngAfterViewInit/Checked 之前触发的任何其他生命周期都将起作用,例如ngOnInitngDoCheckngAfterContentChecked

            所以要修复它,请将 renderWidgetInsideWidgetContainer 移动到 ngOnInit() 生命周期挂钩。

            【讨论】:

            • 为什么要更新ngAfterViewInit/Checked中的属性?
            • 实际上它没有更新我将输入数据设置为视图模型,最后所有属性都通过这个模型绑定,如包装类。当我直接绑定 @Input 数据上下文时,我得到了同样的错误。是否没有解决方法可以通过任何生命周期事件消除此错误。让我尝试您的答案中提到的 ngAfterViewInit/Checked
            • 试试ngOnInit or ngDoCheck or ngAfterContentChecked. 如果还是不行,创建一个plunker
            • hmm..这对我来说很奇怪,根据我的理解,这是非常简单的场景。我怀疑这是因为动态组件加载器。让我尝试创建一个 plunker。
            • 对我来说,只有在我使用引用变量而不是目录之后才能在 ngOnInit 中工作,就像这里解释的 stackoverflow.com/questions/48330760/…
            猜你喜欢
            • 2020-06-16
            • 2020-01-07
            • 2021-01-31
            • 1970-01-01
            • 2018-10-23
            • 2021-09-15
            • 2017-10-19
            • 2018-11-06
            • 2019-02-19
            相关资源
            最近更新 更多