简体   繁体   English

使用时Angular 6上的ExpressionChangedAfterItHasBeenCheckedError <mat-tab>

[英]ExpressionChangedAfterItHasBeenCheckedError on Angular 6 while using <mat-tab>

I have the following Error with Angular 6 我对Angular 6有以下错误

Component 零件

<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>

when I remove the second mat-tab the error disappears 当我删除第二个mat-tab ,错误消失

Into other similar components, I didn't put the 2 forms into a mat-tab-group and mat-tab and don't have this error. 在其他类似的组件中,我没有将2个表单放入mat-tab-groupmat-tab并且没有出现此错误。

Spent a while discovering where is the difference. 花了一段时间发现差异在哪里。

Error into console 控制台出错

ExpressionChangedAfterItHasBeenCheckedError: Expression has changed after it was checked. ExpressionChangedAfterItHasBeenCheckedError:表达式在检查后发生了变化。 Previous value: 'ng-valid: true'. 上一个值:'ng-valid:true'。 Current value: 'ng-valid: false'. 当前值:'ng-valid:false'。

Environment 环境

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

ts file (export class ElectricityRateListComponent extends SelectableEntitiesListComponent) 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
}

export abstract class SelectableEntitiesListComponent extends EntityListComponent { export abstract class SelectableEntitiesListComponent extends 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()


}

export abstract class EntityListComponent extends ReloadableComponent implements AfterViewInit, OnInit { export abstract class EntityListComponent extends 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);
    });
}


}

Highly related to this post 这篇文章密切相关

Reason Behind The Error: 错误背后的原因:

I don't think this error is related to mat-tab. 我不认为这个错误与mat-tab有关。 This error is usually related to the initial development phase where there are uses of lifecycle hooks like ngAfterViewInit . 此错误通常与初始开发阶段有关,其中有使用生命周期钩子,如ngAfterViewInit Quoting directly from Angular Blog- 直接从Angular博客引用 -

This type of error usually shows up beyond the initial development stages, when we start to have some more expressions in our templates, and we have typically started to use some of the lifecycle hooks like AfterViewInit. 这种类型的错误通常会出现在初始开发阶段之外,当我们开始在模板中有更多表达式时,我们通常开始使用一些生命周期钩子,如AfterViewInit。

You can't use paginator reference in ngAfterViewInit() and modify the Data Source immediately, because that will trigger a further modification of the data but Angular view generation process is not finished yet, so its not clear if the value of the variable that you are using as an expression in the template should modified the one or the previous one. 你不能在ngAfterViewInit()中使用paginator引用并立即修改数据源,因为那会触发数据的进一步修改但是Angular视图生成过程还没有完成,所以不清楚变量的值是否是你的用作模板中的表达式应该修改一个或前一个。

Possible Solution: 可能解决方案

In order to solve this issue, we need to let Angular first display the data with the loading flag set to false. 为了解决这个问题,我们需要让Angular首先显示加载标志设置为false的数据。

So, a possible solution is to use setTimeOut or delay(0) before sorting the datasource in ngAfterViewInit() . 因此,一种可能的解决方案是在ngAfterViewInit()对数据源进行排序之前使用setTimeOutdelay(0) ngAfterViewInit()

Reason Why This Solution Works: 此解决方案的原因:

  • The initial value of the flag is false, and so the loading indicator will NOT be displayed initially. 标志的初始值为false,因此最初不会显示加载指示器。
  • ngAfterViewInit() gets called, but the data source is not immediately called, so no modifications of the loading indicator will be made synchronously via ngAfterViewInit() . ngAfterViewInit() ,但不立即调用数据源,因此不会通过ngAfterViewInit()同步修改加载指示符。

  • Angular then finishes rendering the view and reflects the latest data changes on the screen, and the Javascript VM turn completes. Angular然后完成呈现视图并反映屏幕上的最新数据更改,并且Javascript VM转向完成。

  • One moment later, the setTimeout() call (also used inside delay(0) ) is triggered, and only then the data source loads its data. 片刻之后,触发了setTimeout()调用(也在delay(0)内部使用),只有数据源加载其数据。
  • the loading flag is set to true, and the loading indicator will now be displayed. 加载标志设置为true,现在将显示加载指示器。
  • Angular finishes rendering the view, and reflects the latest changes on the screen, which causes the loading indicator to get displayed. 角度完成呈现视图,并反映屏幕上的最新更改,这会导致显示加载指示器。

Resources: 资源:

To understand the issue more deeply, please have a look at this documentation from where I have quoted. 要更深入地了解这个问题,请查看我引用的文档 Here the overall scenario is explained with example. 这里以示例解释整个场景。

You can also have a look at this answer, where using ngAfterContentInit in place of ngAfterViewInit is listed as another possible solution. 您还可以查看答案,其中使用ngAfterContentInit代替ngAfterViewInit作为另一种可能的解决方案。

I hope this would help. 我希望这会有所帮助。

UPDATE: 更新:

Alternative Solutions: 替代方案:

As @jo_va mentioned in the comment, there are other possible solutions to this problem. 正如@jo_va在评论中提到的,这个问题还有其他可能的解决方案。

  1. In place of using setTimeOut() , changeDetector.detectChanges() can also be used. 代替使用setTimeOut() ,也可以使用changeDetector.detectChanges()

    Here I am directly explaining from @jo_va's suggestion: 在这里,我直接从@ jo_va的建议中解释:

    It could be interesting to mention changeDetector.detectChanges() where changeDector is an injected ChangeDetectorRef. 提及changeDetector.detectChanges()可能很有意思,其中changeDector是一个注入的ChangeDetectorRef。 This is another widely used solution to this problem, and I think cleaner than setTimeout. 这是另一个广泛使用的解决方案,我觉得比setTimeout更干净。

  2. And also Promise.resolve might be another alternative to setTimeout . Promise.resolve也可能是setTimeout另一种替代方案。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM