gpt4 book ai didi

angular - 使用 时 Angular 6 上的 ExpressionChangedAfterItHasBeenCheckedError

转载 作者:太空狗 更新时间:2023-10-29 18:22:06 26 4
gpt4 key购买 nike

我在使用 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: Expression has changed after it was checked. Previous value: 'ng-valid: true'. Current value: '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 extends ReloadableComponent implements 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

最佳答案

错误背后的原因:

我认为这个错误与 mat-tab 无关。此错误通常与初始开发阶段相关,其中使用了生命周期 Hook ,如 ngAfterViewInit。直接引用自 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.

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

可能的解决方案:

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

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

此解决方案有效的原因:

  • The initial value of the flag is false, and so the loading indicator will NOT be displayed initially.
  • ngAfterViewInit() gets called, but the data source is not immediately called, so no modifications of the loading indicator will be made synchronously via ngAfterViewInit().

  • Angular then finishes rendering the view and reflects the latest data changes on the screen, and the Javascript VM turn completes.

  • One moment later, the setTimeout() call (also used inside delay(0)) is triggered, and only then the data source loads its data.
  • the loading flag is set to true, and the loading indicator will now be displayed.
  • Angular finishes rendering the view, and reflects the latest changes on the screen, which causes the loading indicator to get displayed.

资源:

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

你也可以看看this答案,其中 using ngAfterContentInit 代替 ngAfterViewInit 被列为另一种可能的解决方案。

希望对您有所帮助。

更新:

替代解决方案:

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

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

    这里我直接根据@jo_va的建议解释:

    It could be interesting to mention changeDetector.detectChanges() where changeDector is an injected ChangeDetectorRef. This is another widely used solution to this problem, and I think cleaner than setTimeout.

  2. 还有 Promise.resolve 可能是另一种替代方法 设置超时

关于angular - 使用 <mat-tab> 时 Angular 6 上的 ExpressionChangedAfterItHasBeenCheckedError,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/54611631/

26 4 0
Copyright 2021 - 2024 cfsdn All Rights Reserved 蜀ICP备2022000587号
广告合作:1813099741@qq.com 6ren.com