【问题标题】:Mat-Datepicker custom component ExpressionChangedAfterItHasBeenChecked ErrorMat-Datepicker 自定义组件 ExpressionChangedAfterItHasBeenChecked 错误
【发布时间】:2020-01-01 20:12:05
【问题描述】:

我正在制作一个自定义日期选择器组件,以便它可以与本地化和 moment.js 以及响应式表单一起使用。

我已经设置了组件,但是在DateChange事件之后,它退出区域后,它会抛出一个错误:

ERROR Error: ExpressionChangedAfterItHasBeenCheckedError: Expression has changed after it was checked. Previous value: 'ng-valid: true'. Current value: 'ng-valid: false'.

目前我不知道如何修复它,尽管我尝试使用 ChangeDetectorRef 修复它,这使我的验证无法触发,this.control.updateValueAndValidty() 也会发生类似的事情。

毕竟,它发生的时间比我触发任何事情要晚很多。我想这是输入元素的问题,所以我会尝试同时捕捉它的更改事件。

所以这是date-picker.component.ts

import { MomentDateAdapter } from '@angular/material-moment-adapter';
import { Component, OnInit, forwardRef, Input, Self, Optional, ChangeDetectorRef } from '@angular/core';
import { ControlValueAccessor, NG_VALUE_ACCESSOR, Validator, NG_VALIDATORS, FormControl, NgControl, FormControlName } from '@angular/forms';
import * as _moment from 'moment';
import { MatDatepickerInputEvent, MAT_DATE_FORMATS, DateAdapter, MAT_DATE_LOCALE } from '@angular/material';
import { default as _rollupMoment } from 'moment';
const moment = _rollupMoment || _moment;

export const DATE_FORMATS = {
  parse: {
    dateInput: 'DD.MM.YYYY.',
  },
  display: {
    dateInput: 'DD.MM.YYYY.',
    monthYearLabel: 'MMM YYYY',
    dateA11yLabel: 'LL',
    monthYearA11yLabel: 'MMMM YYYY',
  },
};


@Component({
  selector: 'app-date-picker',
  templateUrl: './date-picker.component.html',
  styleUrls: ['./date-picker.component.scss'],
  providers: [
    { provide: MAT_DATE_LOCALE, useValue: 'sr' },
    { provide: DateAdapter, useClass: MomentDateAdapter, deps: [MAT_DATE_LOCALE] },
    { provide: MAT_DATE_FORMATS, useValue: DATE_FORMATS }
  ]
})
export class DatePickerComponent implements ControlValueAccessor {
  control: FormControl;
  private onChange = (value: any) => { };
  private onTouched = (value: any) => { };
  @Input() dateValue: string = null;
  @Input() errorMessage: string = "This is required.";
  @Input() public placeholder: string = null;
  @Input() public minDate: string = null;
  @Input() public maxDate: string = null;
  @Input() public startDate: string = null;
  @Input() public label: string = null;

  constructor(@Optional() @Self() private ngControl: NgControl, @Optional() private controlName: FormControlName, private cdr: ChangeDetectorRef) {
    if(this.ngControl != null) {
      this.ngControl.valueAccessor = this;
    }
  }

  ngOnInit() {
    moment.locale('sr');
    this.control = this.controlName.control;
  }

  onDateChange(event: MatDatepickerInputEvent<Date>) {
    this.dateValue = moment(event.value).format();
    this.onChange(event.value);
  }

  // get errorState() {
  //   debugger;

  //   if(!this.dateValue){
  //     return this.ngControl.errors !== null && !!this.ngControl.touched;
  //   }
  //   return false;
  // }

  // ControlValueAccessor methods
  writeValue(value: any): void {
    if(value !== undefined || value !== '') {
      this.dateValue = moment(value).format();
    }
  }

  registerOnChange(fn) {
    this.onChange = fn;
  }

  registerOnTouched() {}
  // registerOnTouched(fn) {
  //   this.onTouched = fn;
  // }


}

这是date-picker.component.html

<div class="date-picker">
    <mat-form-field class="full-width">

      <mat-label>{{ label }}</mat-label>
      <input #dpInput matInput
        [matDatepicker]="componentDatePicker"
        (dateChange)="onDateChange($event)"
        [formControl]="control"
        [value]="dateValue"
        [min]="minDate"
        [max]="maxDate"
        [placeholder]="placeholder"
        [formControl]='control'>

      <mat-datepicker-toggle matSuffix [for]="componentDatePicker"></mat-datepicker-toggle>
      <mat-datepicker #componentDatePicker></mat-datepicker>
      <mat-error [innerHTML]="errorMessage"></mat-error>

    </mat-form-field>
</div>

我正在通过返回带有值或nullnew Date(year, month, day) 对象来更改父组件中的minmax 值。同一个选择器有多个实例,所以我发送字符串值来区分它们。

<app-date-picker
   [minDate]="getMinDate('datepickerIdentifier')"
   [maxDate]="getMaxDate('datepickerIdentifier')"
   [placeholder]="'Placeholder string'"
   [label]="'Label string'"
   [errorMessage]="'Error string'"
   formControlName="applicationEnd"
   required>
 </app-date-picker>

我正在尝试构建可重用的 datepicker 组件,该组件将具有本地化、特定的日期格式,并且可以轻松地合并到响应式表单中,因此我可以使用其余的表单数据对其进行验证,并显示 Validation.required 错误.

很遗憾,我不知道从哪里开始调试。

编辑:我尝试进行堆栈闪电战,但它给了我很多错误,但仍然是:https://stackblitz.com/edit/angular-aylptw

【问题讨论】:

    标签: angular angular-material reactive-forms controlvalueaccessor mat-datepicker


    【解决方案1】:
      onDateChange(event: MatDatepickerInputEvent<Date>) {
        this.dateValue = moment(event.value).format();
        this.onChange(event.value); <= THIS WAS THE PROBLEM
      }
    

    看起来,在删除 this.onChange(event.value) 之后,验证和组件都开始正常工作了。

    编辑:它消除了我遇到的其他问题,但仍然抛出ExpressionChangedAfterItHasBeenCheckedError: Expression has changed after it was checked. Previous value: 'ng-valid: true'. Current value: 'ng-valid: false'.

    【讨论】:

    • 使用我上面的解决方案,您输入的日期将是转换后的日期,您不必在onDateChange() 中重新格式化它。我知道已经快一年了,但这会 100% 奏效
    • @Manoj 谢谢你的帮助!我不太明白 Change Detection strategy 的概念。现在我可以看到,在您的帮助下,这正是我正在寻找的,只是没有找到/使用 onPush 方法。幸运的是,我现在可能需要这个,所以感谢您的出色回答:D
    【解决方案2】:

    我做到了

    当您的 HTML 中的表达式在 Angular 已经检查或比较 oldValue !== newValue 后发生更改时,会抛出此 ExpressionChangedAfterItHasBeenChecked。将您的更改检测策略设置为onPush,如下所示,并触发手动更改检测,就像最后一个代码sn-p中解释的那样。

    @Component({
                       ...                   
      changeDetection: ChangeDetectionStrategy.OnPush
    })
    

    您正在关注 Angular 材料文档,这很好。

    import { MatDateFormats } from '@angular/material/core'; //import MatDateFormats 
    
    export const APP_DATE_FORMATS: MatDateFormats = {
    parse: {
      dateInput: 'MM/DD/YYYY', //whichever format you are tyring, define here
    },
    display: {
      dateInput: 'MM/DD/YYYY', //change accordingly, to display
      monthYearLabel: 'MMM YYYY',
      dateA11yLabel: 'LL',
      monthYearA11yLabel: 'MMMM YYYY',
      }
    };
        
    

    请尝试在不同的文件中创建上述代码,这样更易​​于维护。

    import { DateAdapter, MAT_DATE_FORMATS, MAT_DATE_LOCALE } from '@angular/material/core';
    import { APP_DATE_FORMATS } from '../../helpers/datepicker.format';
    import { MomentDateAdapter, MAT_MOMENT_DATE_ADAPTER_OPTIONS } from '@angular/material-moment-adapter'; 
    //register the imports respectively which are going to be addressed in providers
    
        
    providers: [
      { provide: DateAdapter, useClass: MomentDateAdapter },
      {
        provide: MAT_DATE_FORMATS, useValue: APP_DATE_FORMATS,
        deps: [
         MAT_DATE_LOCALE,
         MAT_MOMENT_DATE_ADAPTER_OPTIONS
        ]
      }
     ]
    

    如上所述更新您各自的模块提供程序并进行反应式表单验证,如果您遇到Error: ExpressionChangedAfterItHasBeenChecked,则从 `angular/core 定义以下 ChangeDetectorRef 以在各自的组件中解决。谢谢。

    constructor(private _cdr: ChangeDetectorRef) {}
            
    insideYourValidations() {
     setTimeout(() => {
      this._cdr.detectChanges() //call to update/detect your changes
     }, 1500);
    }
    

    有时当我们获得ExpressionchangeError 时,日期选择器可能不会更新日期值,而ChangeDetectorRef 如果用户尝试手动输入而不使用日期选择器,则表单输入值会更新。

    这个helped我,解决我的问题。 zmag谢谢

    【讨论】:

    • 这里超时有什么特殊原因吗?
    • 请阅读mokkapps.de/blog/… 以获得更好的理解。谢谢。
    猜你喜欢
    • 2022-10-07
    • 2019-10-25
    • 1970-01-01
    • 1970-01-01
    • 2022-01-03
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多