如何管理Angular2“;表达式在被选中后已更改;组件属性依赖于当前日期时间时发生异常

如何管理Angular2“;表达式在被选中后已更改;组件属性依赖于当前日期时间时发生异常,angular,typescript,time,styles,components,Angular,Typescript,Time,Styles,Components,我的组件具有依赖于当前日期时间的样式。在我的组件中,我有以下函数 private fontColor( dto : Dto ) : string { // date d'exécution du dto let dtoDate : Date = new Date( dto.LastExecution ); (...) let color = "hsl( " + hue + ", 80%, " + (maxLigness - lightnessAmp) +

我的组件具有依赖于当前日期时间的样式。在我的组件中,我有以下函数

  private fontColor( dto : Dto ) : string {
    // date d'exécution du dto
    let dtoDate : Date = new Date( dto.LastExecution );

    (...)

    let color =  "hsl( " + hue + ", 80%, " + (maxLigness - lightnessAmp) + "%)";

    return color;
  }
lightnessAmp
根据当前日期时间计算。如果
dtoDate
在过去24小时内,颜色会发生变化

准确的误差如下所示:

表达式在选中后已更改。以前的值:“hsl(123,80%,49%)”。当前值:“hsl(123,80%,48%)”

我知道只有在检查值时,异常才会出现在开发模式中。如果检查的值与更新的值不同,则引发异常

因此,我尝试使用以下钩子方法在每个生命周期更新当前日期时间,以防止出现异常:

  ngAfterViewChecked()
  {
    console.log( "! changement de la date du composant !" );
    this.dateNow = new Date();
  }

…但没有成功。

在更改后显式运行更改检测:

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

constructor(private cdRef:ChangeDetectorRef) {}

ngAfterViewChecked()
{
  console.log( "! changement de la date du composant !" );
  this.dateNow = new Date();
  this.cdRef.detectChanges();
}
TL;DR

ngAfterViewInit() {
    setTimeout(() => {
        this.dateNow = new Date();
    });
}
虽然这是一种解决方法,但有时确实很难用更好的方式解决这个问题,所以如果使用这种方法,不要责怪自己。没关系

示例:初始问题[],通过
setTimeout()
[]解决


如何避免

通常,此错误通常发生在添加某个位置(甚至在父/子组件中)后
ngAfterViewInit
。所以,第一个问题是问你自己——我能不能没有
ngAfterViewInit
?也许您将代码移动到某个地方(
ngAfterViewChecked
可能是另一种选择)

示例:[]


同时

另外,
ngAfterViewInit
中影响DOM的异步内容也可能导致这种情况。也可以通过
setTimeout
或在管道中添加
delay(0)
操作符来解决:

ngAfterViewInit() {
  this.foo$
    .pipe(delay(0)) //"delay" here is an alternative to setTimeout()
    .subscribe();
}
示例:[]


读得好


关于如何调试它以及它为什么会发生的好文章:

,正如@leocaseiro在上所提到的

我找到了3种解决方案,适用于那些寻求简单解决方案的人

1) 从
ngAfterViewInit
移动到
ngAfterContentInit

2) 与
ChangeDetectorRef
as组合使用后移到
NGAViewChecked
关于#14748的建议(评论)

3) 继续使用ngOnInit(),但在之后调用
changedtectorref.detectChanges()
你的改变


我认为您可以想象的最好、最干净的解决方案是:

@组件({
选择器:“应用程序我的组件”,
模板:`{{myData?.anyfield}}

`, 样式:[''] } ) 导出类MyComponent实现OnInit{ 私人myData; 构造函数(私有myService:myService){} 恩戈尼尼特(){ /* 异步..等待 清除ExpressionChangedTerithasBeenCheckedError异常。 */ this.myService.myObservable.subscribe( 异步(数据)=>{this.myData=await data} ); } }

使用Angular 5.2.9进行测试,我使用了很多次

Promise.resolve(数据)。然后(()=>{
控制台日志(“!组件日期更改!”);
this.dateNow=新日期();
this.cdRef.detectChanges();

});她你去两个解决方案

1.将ChangeDetectionStrategy修改为OnPush 对于这个解决方案,您基本上是在告诉angular:

停止检查更改;只有在我知道有必要的时候我才会这样做

快速解决方案:

修改组件,使其使用
ChangeDetectionStrategy.OnPush

@Component({
  selector: 'app-child',
  templateUrl: './child.component.html',
  changeDetection: ChangeDetectionStrategy.OnPush
})
export class ChildComponent implements OnInit {
    // ...
}
有了这个,事情似乎不再有效了。这是因为从现在起,您必须手动调用
detectChanges()

this.cdr.detectChanges();
以下链接帮助我正确理解ChangeDetectionStrategy:

2.理解表达式更改后的检查错误 下面是tomonari_t关于这个错误原因的一个小摘录,我试图只包括帮助我理解这个错误的部分

完整的文章展示了关于这里显示的每一点的真实代码示例

根本原因是angular lifecycle的:

每次操作后,Angular都会记住它用来执行的值 手术。它们存储在的oldValues属性中 组件视图

对所有部件进行检查后,开始 下一个摘要周期,但它不执行操作,而是将当前值与它记忆的值进行比较 上一个摘要周期

在摘要循环中检查以下操作:

检查传递给子组件的值是否与 用于更新这些组件的属性的值 现在

检查用于更新DOM元素的值是否与 用于更新这些元素的值现在执行 一样

检查所有子组件

因此,当比较的值不同时,就会抛出错误。
,blogger说:

罪魁祸首总是子组件或指令

最后,这里是一些通常会导致此错误的真实示例:

  • 共享服务
  • 同步事件广播
  • 动态组件实例化
每个示例都可以找到(plunkr),在我的例子中,问题是动态组件实例化

另外,根据我自己的经验,我强烈建议大家避免使用
setTimeout
解决方案,因为我的情况导致了“几乎”无限循环(21次调用,我不愿意告诉您如何激发它们)

我建议您始终牢记角度生命周期,以便在每次修改另一个组件的值时考虑它们将如何受到影响。有了这个错误,Angular告诉您:

你可能做得不对,你确定你是对的吗

该博客还说:<
@Component({
  selector: 'app-spinner',
  templateUrl: './spinner.component.html',
  styleUrls: ['./spinner.component.scss'],
  changeDetection: ChangeDetectionStrategy.OnPush
})
export class SpinnerComponent implements OnInit, OnDestroy, AfterContentChecked {

  show = false;

  private subscription: Subscription;

  constructor(private spinnerService: SpinnerService, private changeDedectionRef: ChangeDetectorRef) { }

  ngOnInit() {
    this.subscription = this.spinnerService.spinnerState
      .subscribe((state: SpinnerState) => {
        this.show = state.show;
      });
  }

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

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

}
calendarView$ = new BehaviorSubject<View>(null);
  ngAfterViewInit(): void {
    // ViewChild is available here, so get the JS API
    this.calendarApi = this.calendar.getApi();
  }

  ngAfterViewChecked(): void {
    // The view has been checked and I know that the View object from
    // fullcalendar is available, so emit it.
    this.calendarView$.next(this.calendarApi.view);
  }
export class SomeComponent {

    header: string;

}
ngAfterViewInit() { 

    // change variable value here...
}
ngAfterContentInit() {

    // change variable value here...
}