我在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-group
和mat-tab
中,也没有此错误。
花了一会儿发现区别在哪里。
ExpressionChangedAfterItHaHasBeenCheckedError:检查表达式后,表达式已更改。先前的值:“ ng-valid:true”。当前值:“ ng-valid:false”。
Angular CLI: 6.2.8
Node: 11.9.0
OS: linux x64
Angular:
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
}
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()
}
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);
});
}
}
高度相关
答案 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在评论中提到的那样,还有其他可能的解决方案。
代替setTimeOut()
,changeDetector.detectChanges()
也可以使用。
在这里,我直接根据@jo_va的建议进行解释:
提到changeDetector.detectChanges()可能很有趣 其中changeDector是注入的ChangeDetectorRef。这是另一个 这个问题的解决方案被广泛使用,我认为比 setTimeout。
Promise.resolve
也可能是
setTimeout
。