5

著名的select2终于有热心的大牛构建了angular版本,并命名为ng-select2。但(猜想)由于其核心的代码依赖于jquery,所以在结合formControl使用时,会发生如下异常:Error: ExpressionChangedAfterItHasBeenCheckedError: Expression has changed after it was checked. Previous value for 'ng-pristine': 'true'. Current value: 'false'.

情景再现

比如我们有以下组件树:
image.png

一个普通的A组件,一个可以实现了ControlValueAccessor接口并声明为FormControl的B组件,一个引用的ng-select2组件。

其中A组件包含B组件、B组件又包含ng-select2组件。

ng-select2组件中的数据列表是通过资源请求得到的列表,所以是异步数据,B组件V层示例如下:

<ng-select2
  [value]="datum"
  [data]="data"
  (valueChanged)="onChanged($event)"
</ng-select2>

B获取select2使用的数据列表data示例代码如下:

ngOnInit(): void {
    this.service.getAll()
        .subscribe(data => {
            // 在这设置ng-select2的数据列表及已选择的值
            this.data = data;
        });
}

此时在A组件将会发生如下错误:Error: ExpressionChangedAfterItHasBeenCheckedError: Expression has changed after it was checked. Previous value for 'ng-pristine': 'true'. Current value: 'false'.

[error] 此示例违反了angular的单元向数据流原则,应当在实际开发中予以规避。

原因猜想

我们知道,由于在大牛们会详细的解释异常发生的原因,所以解决问题的第一步也是最重要的一步便是翻译:
严重错误:表达式被检测完毕以后却发生了变更错误:当表达式被检测完毕以后却又发生了变更。检测完毕时,检测的'ng-pristine'的值为'true'. 当前值为:'false'
同时pristine可译为原始的

所以我猜错主大概是如下发生的:
image.png

也就是说:将A组件在渲染完毕的情况下,发现FormControl的值发生了变化,则会发生ExpressionChangedAfterItHasBeenCheckedError异常。

解决方法

ng-select2的官方demo中给出了当列表值异步时的解决方案,但遗憾的是该方法同样会引发上述异常。

该异常发生的主要原因是由于A组件处于渲染完毕,使得A中做为FormControl的组件B同样处于tHasBeenChecked状态,所以解决该问题也可以如下着手:
image.png

如上图所示,当组件B在接收到异步数据时,为了防止其子组件ng-select2进行数据弹射时A组件处于渲染完毕状态,我们在此手动的触发重新渲染所有组件的方法。此时将A组件接收到最新的值时,其状态处于渲染中,当然也就不会出现HasBeenChecked异常了。

代码如下:

constructor(private service: Service,
  private changeDetectorRef: ChangeDetectorRef) {
}

ngOnInit(): void {
    this.service.getAll()
        .subscribe(data => {
            // 在这设置ng-select2的数据列表
            this.data = data;
            // 手动调用组件渲染方法,重新渲染各组件
            this.changeDetectorRef.detectChanges();
        });

总结

排错的路千万条,翻译、瞎猜第一条。


潘杰
3.1k 声望239 粉丝