【问题标题】:ExpressionChangedAfterItHasBeenCheckedError on Angular 6 while using <mat-tab>Angular 6 上的 ExpressionChangedAfterItHasBeenCheckedError 使用 <mat-tab>
【发布时间】:2019-07-03 19:55:26
【问题描述】:

Angular 6 出现以下错误

组件

<mat-tab-group [(selectedIndex)]="selectedTabIndex">
  <mat-tab label="Add">
    <ng-template matTabContent>
      <form [formGroup]="entityAddFormGroup">
        <dynamic-material-form [group]="entityAddFormGroup" [model]="entityAddFormCtlModelArray"></dynamic-material-form>
        <button (click)="buttonAddEntityClicked(entityAddFormGroup.value)">Add</button>
      </form>
    </ng-template>
  </mat-tab>
  <mat-tab *ngIf="entityEditFormGroup && currentEntity" label="Edit #{{currentEntity.id}}">
    <!-- TODO correct bug with -->
    <ng-template matTabContent>
      <form [formGroup]="entityEditFormGroup">
        <!-- <h2 i18n>Edit #{{currentEntity.id}}</h2> -->
        <dynamic-material-form [group]="entityEditFormGroup" [model]="entityEditFormCtlModelArray"></dynamic-material-form>
        <button (click)="buttonEditEntityClicked(entityEditFormGroup.value)">Save</button>
      </form>
    </ng-template>
  </mat-tab>
</mat-tab-group>

当我删除第二个 mat-tab 时,错误消失了

到其他类似的组件中,我没有把这2个表格放到mat-tab-groupmat-tab中,也没有这个错误。

花了一段时间才发现不同之处。

控制台出错

ExpressionChangedAfterItHasBeenCheckedError:表达式在检查后已更改。以前的值:'ng-valid: true'。当前值:'ng-valid: false'。

环境

Angular CLI: 6.2.8
Node: 11.9.0
OS: linux x64
Angular: 

ts 文件(导出类 ElectricityRateListComponent 扩展 SelectableEntitiesListComponent)

public displayedColumnsArray = [
    'select',
    'id',
    'energyRate',
    'mainTransmissionRate',
    'publicServiceRate',
    'validityStartDate',
    'validityEndDate',
    'electricityType',
    'city',
]; // Gives the order of the columns
public statusMessage: string = ''
public selectedTabIndex: number = 0


protected _elTypeAddSelect: DBEntitySelect<Enumerate> //ElectricityType: Enumerate
protected _elTypeEditSelect: DBEntitySelect<Enumerate> //ElectricityType: Enumerate

protected _cityAddSelect: DBEntitySelect<Enumerate> //City: Enumerate
protected _cityEditSelect: DBEntitySelect<Enumerate> //City: Enumerate

constructor(
    protected router: Router,
    public messageService: MessageService,
    protected logger: LoggerService,
    protected route: ActivatedRoute,
    protected entitiesService: ElectricityRateService,
    protected enumeratesService: EnumerateService,
    protected formBuilder: FormBuilder,
    public formService: DynamicFormService,
    iconRegistry: MatIconRegistry,
    sanitizer: DomSanitizer,
    // private location: Location
) {
    super(router, messageService, logger, route, entitiesService, formBuilder, formService, iconRegistry, sanitizer, new ElectricityRate());

    (...)
}



/**
* Common to add and edit forms
*
* @param aStrangeObject
*/
protected _getCommonFormControlModel(aStrangeObject: Enumerate): DynamicFormControlModel[] {
    let lEntity: ElectricityRate = new ElectricityRate().deserialize(
    aStrangeObject
    )
    console.debug(
    "-----getAddFormControlModel->",
    aStrangeObject,
    lEntity.validityStartDate.constructor.name,
    lEntity.validityEndDate.constructor.name
    )
    const result: DynamicFormControlModel[] = [
    new DynamicInputModel({
        id: "energyRate",
        label: "Energy Rate",
        value: lEntity.energyRate,
        inputType: DYNAMIC_FORM_CONTROL_INPUT_TYPE_NUMBER,
        min: ElectricityRate.MIN_ELECTRICITY_RATE,
        max: ElectricityRate.MAX_ELECTRICITY_RATE,
        placeholder: "Energy Rate"
    }),
    new DynamicInputModel({
        id: "mainTransmissionRate",
        label: "Transmission Rate",
        inputType: DYNAMIC_FORM_CONTROL_INPUT_TYPE_NUMBER,
        min: ElectricityRate.MIN_ELECTRICITY_RATE,
        max: ElectricityRate.MAX_ELECTRICITY_RATE,
        value: lEntity.mainTransmissionRate.toString(),
        placeholder: "Transmission Rate"
    }),
    new DynamicInputModel({
        id: "publicServiceRate",
        label: "Public Service Rate",
        inputType: DYNAMIC_FORM_CONTROL_INPUT_TYPE_NUMBER,
        min: ElectricityRate.MIN_ELECTRICITY_RATE,
        max: ElectricityRate.MAX_ELECTRICITY_RATE,
        value: lEntity.publicServiceRate.toString(),
        placeholder: "Public Service Rate"
    }),
    new DynamicInputModel({
        id: "validityStartDate",
        label: "Validity start date",
        inputType: DYNAMIC_FORM_CONTROL_INPUT_TYPE_DATE,
        maxLength: 10,
        value: MiscHelper.dateToDynamicInputDate(lEntity.validityStartDate),
        placeholder: "Validity start date"
    }),
    new DynamicInputModel({
        id: "validityEndDate",
        label: "Validity end date",
        inputType: DYNAMIC_FORM_CONTROL_INPUT_TYPE_DATE,
        value: MiscHelper.dateToDynamicInputDate(lEntity.validityEndDate),
        placeholder: "Validity end date"
    })
    ]
    return result

}

/**
* called by SelectableEntitiesListComponent->onInit
*
* @param aStrangeObject
*/
protected _getAddFormControlModel(aStrangeObject: Enumerate): DynamicFormControlModel[] {
    //console.debug('getAddFormControlModel->aStrangeObject:', aStrangeObject)
    let lEntity: Enumerate = new Enumerate().deserialize(aStrangeObject)
    console.debug('-----getAddFormControlModel->aStrangeObject, lEntity:', aStrangeObject, lEntity)
    //Add form fields
    const result: DynamicFormControlModel[] = this._getCommonFormControlModel(aStrangeObject)
    result.push(this._elTypeAddSelect.asDynamicInputModel())
    result.push(this._cityAddSelect.asDynamicInputModel())
    return result
}



/**
* Built onRowClicked
*
* @param anId
* @param aStrangeObject can be a row of dataTable
*/
protected _getEditFormControlModel(aStrangeObject: Enumerate): DynamicFormControlModel[] {
    console.log('getEditFormControlModel:', aStrangeObject)
    let result = this._getCommonFormControlModel(aStrangeObject)
    result = result.concat(DBEntity.getIdFormControlModel('id', aStrangeObject))
    result.push(this._elTypeEditSelect.asDynamicInputModel())
    result.push(this._cityEditSelect.asDynamicInputModel())
    // console.log('getEditFormControlModel:', result)

    return result
}

导出抽象类 SelectableEntitiesListComponent 扩展 EntityListComponent {

public ngOnInit() {
    super.ngOnInit()
    this._setSelects()
}

/**
* redefine
*/
public onReloadClicked(anEvent) {
    super.onReloadClicked(anEvent)
    this._setSelects()
}


/**
* redefine
*/
public afterEntityUpdatedSucessful(){
    super.afterEntityUpdatedSucessful()
    this._setSelects()
}

/**
*
*/
protected abstract _setSelects()


}

导出抽象类 EntityListComponent 扩展 ReloadableComponent 实现 AfterViewInit, OnInit {

protected _currentEntity: D = null // Set to null and not undefined cause of list.component.html tests for it  reason explained https://stackoverflow.com/questions/5076944/what-is-the-difference-between-null-and-undefined-in-javascript
protected abstract displayedColumnsArray: Array<string>; // Gives the order of the columns
public entitiesListTitle = this.constructor.name

// FORMS
entityAddFormGroup: FormGroup;
entityAddFormCtlModelArray: DynamicFormControlModel[];
entityEditFormGroup: FormGroup;
entityEditFormCtlModelArray: DynamicFormControlModel[];

// DATA TABLE variables
dataSource: SseEntityDataSource<D>;
selectionModel = new SelectionModel<D>(true, []);
@ViewChild(MatPaginator) paginator: MatPaginator;
@ViewChild(MatSort) sort: MatSort;

constructor(
    protected router: Router,
    public messageService: MessageService,
    protected logger: LoggerService,
    protected route: ActivatedRoute,
    protected entitiesService: SseEntityService<D>,
    protected formBuilder: FormBuilder,
    public formService: DynamicFormService,
    iconRegistry: MatIconRegistry,
    sanitizer: DomSanitizer,
    public entityPrototype: DBEntity,
    // private location: Location
) {
    super(
    iconRegistry,
    sanitizer,
    )
    if (entityPrototype === undefined || entityPrototype == null){
    throw new Error('constructor error, create me in the caller entityPrototype!')
    }
}

/**
* calls this._getAddFormControlModel() and adds it to entityAddFormCtlModelArray
*/
public ngOnInit() {
    // console.debug('ngOnInit called')
    if (this.entityPrototype === undefined){
    throw new Error('entity-list.component->ngOnInit-> this.entityPrototype is undefined, set it into constructor of descendant')
    }
    this.entitiesListTitle = StringHelper.camelCaseToSpaces(this.constructor.name.replace('Component', ''))


    this.dataSource = new SseEntityDataSource<D>(this.logger, this.entitiesService, this, this.entityPrototype);
    this.setMessageService();
    this.entityAddFormCtlModelArray = this._getAddFormControlModel(this.entityPrototype);
    this.entityAddFormGroup = this.formService.createFormGroup(this.entityAddFormCtlModelArray);

    this.dataSource.loadEntities()
}

protected abstract _getCommonFormControlModel(aStrangeObject: DBEntity): DynamicFormControlModel[]
protected abstract _getAddFormControlModel(aStrangeObject: DBEntity): DynamicFormControlModel[]

public ngAfterViewInit() {
    this.dataSource.paginator = this.paginator;
    this.dataSource.sort = this.sort;
//    this.cdr.detectChanges();
}

get currentEntity(): D {
    return this._currentEntity;
}

set currentEntity(value: D) {
    this._currentEntity = value;
    this.entitiesService.currentEntity = value;
}

/**
* Require dataSource not null
*/
public loadDatasourceWithPaginator() {
    // Init currentEntityId
    try {
    this.dataSource.loadEntities();
    } catch (e) {
    this.messageService.add(new UserMessage('Error loading entities', e, UserMessageType.Error));
    throw e;
    }
}

public applyFilter(filterValue: string) {
    filterValue = filterValue.trim(); // Remove whitespace
    filterValue = filterValue.toLowerCase(); // Datasource defaults to lowercase matches
    this.dataSource.filter = filterValue;
}


/**
* Require dataSource not null
*/
public setMessageService() {
    this.dataSource.messagesForUsers$.subscribe(
    usrMessage => {
        this.messageService.add(usrMessage);
    }
    );
}


abstract onRowClicked(row: any): void;

public buttonAddEntityClicked(dataValues: any) {
    console.debug('buttonAddEntityClicked-------->from Entitylist.components dataValues:', dataValues);
    let lEntity = this.entityPrototype.deserialize(dataValues, false)
    console.debug('buttonAddEntityClicked-------->from Entitylist.components lEntity:', lEntity);
    console.debug('buttonAddEntityClicked-------->from Entitylist.components lEntity.toJSON():', lEntity.toJSON());

    this.entitiesService.addEntityFromFormData(lEntity.toJSON()).subscribe(
    lData => {
        const msg = `Entity added successfully`;
        this.messageService.add(new UserMessage(msg, lData, UserMessageType.Info));
        this.afterEntityUpdatedSucessful()
    },
    lError => {
        const msg = `Entity add Error`;
        console.error('buttonAddEntityClicked->Error:', lError)
        this.messageService.add(new UserMessage(msg, lError, UserMessageType.Error));
        throw lError;
    }
    );
}

public afterEntityUpdatedSucessful(){
    this.loadDatasourceWithPaginator();
}


public buttonEditEntityClicked(jsonStringValues: string) {
    this.logger.debug('buttonAddEntityClicked-> from Entitylist.components:', jsonStringValues);
    let lEntity = this.entityPrototype.deserialize(jsonStringValues, false)
    this.logger.debug('buttonEditEntityClicked-> Entitylist.components: jsonStringValues, lEntity:', jsonStringValues, lEntity);

    this.entitiesService.updateEntityFromFormData(lEntity.toJSON()).subscribe(
    lData => {
        const msg = `Entity updated successfully`;
        this.messageService.add(new UserMessage(msg, lData, UserMessageType.Info));
        this.afterEntityUpdatedSucessful()
    },
    lError => {
        const msg = `Entity update Error`;
        console.error('buttonEditEntityClicked->Error:', lError)
        this.messageService.add(new UserMessage(msg, lError, UserMessageType.Error));
        throw lError;
    }
    );
}


public buttonRemoveSelectedRowsClicked() {
    let toReloadObservable: Observable<Object> = null;
    this.selectionModel.selected.forEach(item => {
    this.logger.debug('Deleting selected item:', item);
    toReloadObservable = this.entitiesService.deleteFromId(item.id);
    toReloadObservable.subscribe(
        data => {
        const msg = `Entity ${item.id} deleted successfully`;
        this.messageService.add(new UserMessage(msg, data, UserMessageType.Info));
        this.afterEntityUpdatedSucessful()
        },
        error => {
        const msg = `Error while deleting entity ${item.id}`;
        this.messageService.add(new UserMessage(msg, error, UserMessageType.Error));
        throw error;
        }
    );
    });
    this.selectionModel = new SelectionModel<D>(true, []);
    this._currentEntity = null;
    // When all are removed reload data source
}

public onReloadClicked(anEvent) {
    this.loadDatasourceWithPaginator();
}

public buttonMasterToggleClicked() {
    this.isAllSelected() ?
    this.selectionModel.clear() :
    this.dataSource.data.forEach(row => this.selectionModel.select(row));
}

public sampleAddButtonClicked() {
    Constants.SAMPLE_COMPANIES_JSON_DATA.forEach( (entity) => {
    // console.log('sampleAddButtonClicked', JSON.stringify(entity));
    this.buttonAddEntityClicked(entity);
    });
}

public isAllSelected() {
    const numSelected = this.selectionModel.selected.length;
    const numRows = this.dataSource.entitiesCount();
    return numSelected === numRows;
}

protected _updateEditFormFields(toUpdate: any) {
    console.log("updateEditFormFields->toUpdate, model", toUpdate, this.entityEditFormCtlModelArray);
    Object.entries(toUpdate).forEach(([key, value]) => {
    // console.log('updateEditFormFields->setting key', key, 'value:', value);
    const inputModel = this.formService.findById(key, this.entityEditFormCtlModelArray) as DynamicInputModel;

    if (inputModel == null) {
        throw new Error('updateEditFormFields->InputModel is null, key ' + key + ' not found into entityEditFormCtlModel val:' + value );
    }
    inputModel.valueUpdates.next(value as string)//If not reloading recreate the formGroup with this.entityAddFormGroup = this.formService.createFormGroup(this.entityAddFormCtlModelArray);
    // inputModel.valueUpdates.subscribe(value => console.log('new value assigned to field: ', newVal));
    // inputModel.disabledUpdates.next(true);
    });
}


}

this post高度相关

【问题讨论】:

标签: angular


【解决方案1】:

错误原因:

我认为这个错误与 mat-tab 无关。此错误通常与初始开发阶段有关,其中使用了诸如ngAfterViewInit 之类的生命周期钩子。直接引用 Angular 博客-

这种类型的错误通常出现在最初的开发之外 阶段,当我们开始在模板中添加更多表达式时, 我们通常已经开始使用一些生命周期钩子,比如 AfterViewInit。

您不能在 ngAfterViewInit() 中使用分页器引用并立即修改数据源,因为这将触发对数据的进一步修改但 Angular 视图生成过程尚未完成,因此不清楚您在模板中用作表达式的变量应该修改一个或前一个。

可能的解决方案:

为了解决这个问题,我们需要让Angular首先显示加载标志设置为false的数据。

因此,一种可能的解决方案是在对ngAfterViewInit() 中的数据源进行排序之前使用setTimeOutdelay(0)

此解决方案有效的原因:

  • 标志的初始值为false,因此加载指示器最初不会显示。
  • ngAfterViewInit() 被调用,但没有立即调用数据源,因此没有修改加载指示器 将通过ngAfterViewInit()同步进行。

  • Angular 然后完成渲染视图并在屏幕上反映最新的数据变化,Javascript VM 轮回完成。

  • 片刻之后,setTimeout() 调用(也在delay(0) 内部使用)被触发,然后数据源才加载它的 数据。
  • 加载标志设置为真,现在将显示加载指示器。
  • Angular 完成了视图的渲染,并在屏幕上反映了最新的变化,这导致加载指示器得到 显示。

资源:

要更深入地了解该问题,请查看我引用的 this documentation。这里用例子来解释整个场景。

您还可以查看this 答案,其中使用ngAfterContentInit 代替ngAfterViewInit 被列为另一种可能的解决方案。

我希望这会有所帮助。

更新:

替代解决方案:

正如评论中提到的@jo_va,这个问题还有其他可能的解决方案。

  1. 代替使用setTimeOut()changeDetector.detectChanges() 也可以使用。

    这里我直接从@jo_va的建议中解释:

    提及 changeDetector.detectChanges() 可能会很有趣 其中 changeDector 是注入的 ChangeDetectorRef。这是另一个 广泛使用的解决方案,我认为比 设置超时。

  2. 还有Promise.resolve 可能是另一种选择 setTimeout.

【讨论】:

  • 提到changeDetector.detectChanges() 可能会很有趣,其中changeDector 是注入的ChangeDetectorRef。这是解决这个问题的另一个广泛使用的解决方案,我认为比setTimeout
  • 您也可以使用Promise.resolve 替代setTimeout
  • 谢谢,@jo_va。我不确定您为这个问题提供的其他两种可能的解决方案。我可以参考你的名字来更新答案吗?
  • 绝对是!最好有一个好的和有据可查的答案,而不是很多
  • 许多 Thx,学到了很多东西,并通过使用 ngAfterContentInit 而不是 ngAfterViewInit 来解决,这似乎是最干净且不是解决方法的解决方案!
猜你喜欢
  • 2022-10-16
  • 2021-05-31
  • 2019-03-02
  • 1970-01-01
  • 2019-02-25
  • 2017-06-15
  • 2020-08-08
  • 2019-06-23
  • 1970-01-01
相关资源
最近更新 更多