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

时间:2019-02-09 22:55:39

标签: angular

我在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中,也没有此错误。

花了一会儿发现区别在哪里。

控制台错误

  

ExpressionChangedAfterItHaHasBeenCheckedError:检查表达式后,表达式已更改。先前的值:“ 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

高度相关

1 个答案:

答案 0 :(得分:3)

错误背后的原因:

我认为此错误与mat-tab不相关。此错误通常与使用诸如ngAfterViewInit之类的生命周期挂钩的初始开发阶段有关。直接从Angular Blog报价-

  

这种类型的错误通常会超出最初的发展范围   阶段,当我们开始在模板中添加更多表达式时,   我们通常已经开始使用一些生命周期挂钩,例如   AfterViewInit。

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

可能的解决方案:

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

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

此解决方案起作用的原因:

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

  •   
  • 然后,Angular完成了视图的呈现并在屏幕上反映了最新的数据更改,并且Javascript VM转换完成了。

  •   
  • 过一会儿,触发了setTimeout()调用(也在delay(0)中使用),然后数据源才加载它   数据。
  •   
  • 加载标记设置为true,现在将显示加载指示器。
  •   
  • Angular完成了视图的渲染,并在屏幕上反映了最新的变化,这导致加载指示器获得   显示。
  •   

资源:

要更深入地了解该问题,请查看我引用的 this documentation 。此处以示例说明整体情况。

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

我希望这会有所帮助。

更新:

替代解决方案:

就像@jo_va在评论中提到的那样,还有其他可能的解决方案。

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

    在这里,我直接根据@jo_va的建议进行解释:

      

    提到changeDetector.detectChanges()可能很有趣   其中changeDector是注入的ChangeDetectorRef。这是另一个   这个问题的解决方案被广泛使用,我认为比   setTimeout。

  2. Promise.resolve也可能是     setTimeout