ExpressionChangedAfterItHasBeenCheckedError 在对话框中预选反应形式时出现异常
ExpressionChangedAfterItHasBeenCheckedError exception when preselect reactive form inside a dialog
我正在尝试使用反应式表单和动态表单数据初始化实现一个对话框。
该对话框位于一个子组件中,由我的主要组件触发。
当我尝试 preselect/fill 表单组件时,出现此错误:
ExpressionChangedAfterItHasBeenCheckedError: Expression has changed after it was checked. Previous value: 'status-success: false'. Current value: 'status-success: true'.
只有当我预选nb-datepicker组件有值时才会出现问题,如果使用null作为值,一切都很好。
这是我的代码:
主要成分
<i (click)="editdialog.bookingToEdit(booking)" class="fas fa-edit"></i>
....
<ngx-booking-edit #editdialog></ngx-booking-edit>
带对话框的子组件
@Component({
selector: 'ngx-booking-edit',
templateUrl: './booking-edit.component.html',
styleUrls: ['./booking-edit.component.scss'],
})
export class BookingEditComponent implements AfterViewInit {
@Output() updateBookingData = new EventEmitter<Booking>();
@ViewChild('dialogedit') editDialog: TemplateRef<any>;
private initialData: InitialDataResponse;
private booking: Booking;
public bookingForm: FormGroup;
public newContractPartner = false;
constructor(private formBuilder: FormBuilder,
private dialogService: NbDialogService,
private initialDataService: InitialDataService,
private toasterService: ToasterService) {
}
ngAfterViewInit() {
this.loadInitData();
}
private loadInitData() {
// REST call init data
this.initialDataService.getInitalData()
.map(resp => resp)
.subscribe(
(data) => {
this.initialData = data.body;
},
(err) => {
console.error('status', err.status);
},
);
}
//this method is called from the main component with an booking object
bookingToEdit(bookingToEdit: Booking) {
this.booking = bookingToEdit;
this.initForm();
this.dialogService.open(this.editDialog, {});
}
private initForm() {
const today: Date = new Date();
this.bookingForm = this.formBuilder.group({
description: [this.booking.description, [Validators.required, MyValidators.nospaceValidator]],
amount: [this.booking.amount, [Validators.required, MyValidators.validateAmount]],
date: [today, Validators.required],
capitalSource: [this.booking.capitalSource.id, Validators.required],
category: [this.booking.subcategory.id, Validators.required],
contractpartner: [this.booking.contractPartner.name, [Validators.required, MyValidators.nospaceValidator]],
privatCheckbox: [this.booking.privatBookingOfUser],
});
}
}
子对话框模板
<toaster-container></toaster-container>
<ng-template #dialogedit let-data let-ref="dialogRef">
<form [formGroup]="bookingForm">
<nb-card>
<nb-card-header>Buchung bearbeiten</nb-card-header>
<nb-card-body>
<div class="row">
<div class="col-md-3">
<div class="form-group">
<div class="input-group input-group-sm">
<input nbInput fullWidth
placeholder="Buchungsdatum"
formControlName="date"
style="line-height: 1rem;"
[ngClass]="{
'status-danger': bookingForm.controls.date.invalid && bookingForm.controls.date.dirty,
'status-success': bookingForm.controls.date.valid && bookingForm.controls.date.dirty }"
[nbDatepicker]="formpicker">
<nb-datepicker #formpicker></nb-datepicker>
</div>
</div>
</div>
<div class="col-md-6">
<div class="form-group input-group-sm">
<input [ngClass]="{
'status-danger': bookingForm.controls.description.invalid && bookingForm.controls.description.dirty,
'status-success': bookingForm.controls.description.valid && bookingForm.controls.description.dirty }"
class="status" fullWidth id="description" nbInput placeholder="Beschreibung"
formControlName="description" type="text"/>
</div>
</div>
<div class="col-md-2">
<div class="form-group input-group-sm">
<input [ngClass]="{
'status-danger': bookingForm.controls.amount.invalid && bookingForm.controls.amount.dirty,
'status-success': bookingForm.controls.amount.valid && bookingForm.controls.amount.dirty }"
class="status" fullWidth nbInput step="0.10" type="number"
formControlName="amount"
placeholder="Betrag" type="text"/>
</div>
</div>
</div>
<div class="row">
<div class="col-md-5">
<div class="form-group input-group-sm">
<input nbInput fullWidth id="typeahead-format" placeholder="Vertragspartner"
formControlName="contractpartner"
type="text" placeholder="Vertragspartner"
[ngClass]="{
'newContractPartner': newContractPartner,
'status-danger': bookingForm.controls.contractpartner.invalid && bookingForm.controls.contractpartner.dirty,
'status-success': bookingForm.controls.contractpartner.valid && bookingForm.controls.contractpartner.dirty }"
[ngbTypeahead]="searchContractPartner" class="status"/>
</div>
</div>
<div class="col-md-4">
<div class="form-group input-group-sm">
<nb-select nbSelect fullWidth [size]="formSize"
formControlName="category" placeholder="Kategorie"
[ngClass]="{
'status-danger': bookingForm.controls.category.invalid && bookingForm.controls.category.dirty,
'status-success': bookingForm.controls.category.valid && bookingForm.controls.category.dirty }">
<option disabled [value]=null>Kategorie</option>
<nb-option-group *ngFor="let category of initialData?.categories" title="{{category.name}}">
<nb-option *ngFor="let subcategory of category.subcategories" [value]="subcategory.id">
{{subcategory.name}}
</nb-option>
</nb-option-group>
</nb-select>
</div>
</div>
<div class="col-md-3">
<div class="form-group input-group-sm">
<nb-select fullWidth [size]="formSize"
formControlName="capitalSource"
[ngClass]="{
'status-danger': bookingForm.controls.capitalSource.invalid && bookingForm.controls.capitalSource.dirty,
'status-success': bookingForm.controls.capitalSource.valid && bookingForm.controls.capitalSource.dirty }">
<nb-option disabled [value]=null>Kapitalquelle</nb-option>
<nb-option *ngFor="let capitalSource of initialData?.capitalSources" [value]="capitalSource.id">
{{capitalSource.description}}
</nb-option>
</nb-select>
</div>
</div>
<div class="col-md-2">
<div class="form-group input-group-sm" style="color: #a1a1e5 !important;">
<nb-checkbox formControlName="privatCheckbox">Privat</nb-checkbox>
</div>
</div>
</div>
</nb-card-body>
<nb-card-footer>
<div class="col-md-12">
<div class="form-group input-group-sm">
<div _ngcontent-c40="" class="container-btn">
<button [disabled]="!bookingForm.valid" nbButton>
Buchung speichern
</button>
</div>
</div>
</div>
</nb-card-footer>
</nb-card>
</form>
</ng-template>
我尝试在 setTimeout 中调用 dialog.open 方法,并尝试在 resolvedPromise.then(() => {...} 中预填充表单数据,但没有成功。
有人知道问题出在哪里吗?
StackBlitz:stackblitz.com/edit/github-lv7hp3
运行 change detection
显式更改后。请在您的组件中添加这段代码,这应该可以解决您的问题。
import { ChangeDetectorRef } from '@angular/core';
constructor(private cdRef:ChangeDetectorRef) {}
ngAfterViewChecked()
{
this.cdRef.detectChanges();
}
Angular 文档 link => https://angular.io/api/core/ChangeDetectionStrategy
GitHub 问题 => https://github.com/angular/angular/issues/15634
我正在尝试使用反应式表单和动态表单数据初始化实现一个对话框。 该对话框位于一个子组件中,由我的主要组件触发。
当我尝试 preselect/fill 表单组件时,出现此错误:
ExpressionChangedAfterItHasBeenCheckedError: Expression has changed after it was checked. Previous value: 'status-success: false'. Current value: 'status-success: true'.
只有当我预选nb-datepicker组件有值时才会出现问题,如果使用null作为值,一切都很好。
这是我的代码:
主要成分
<i (click)="editdialog.bookingToEdit(booking)" class="fas fa-edit"></i>
....
<ngx-booking-edit #editdialog></ngx-booking-edit>
带对话框的子组件
@Component({
selector: 'ngx-booking-edit',
templateUrl: './booking-edit.component.html',
styleUrls: ['./booking-edit.component.scss'],
})
export class BookingEditComponent implements AfterViewInit {
@Output() updateBookingData = new EventEmitter<Booking>();
@ViewChild('dialogedit') editDialog: TemplateRef<any>;
private initialData: InitialDataResponse;
private booking: Booking;
public bookingForm: FormGroup;
public newContractPartner = false;
constructor(private formBuilder: FormBuilder,
private dialogService: NbDialogService,
private initialDataService: InitialDataService,
private toasterService: ToasterService) {
}
ngAfterViewInit() {
this.loadInitData();
}
private loadInitData() {
// REST call init data
this.initialDataService.getInitalData()
.map(resp => resp)
.subscribe(
(data) => {
this.initialData = data.body;
},
(err) => {
console.error('status', err.status);
},
);
}
//this method is called from the main component with an booking object
bookingToEdit(bookingToEdit: Booking) {
this.booking = bookingToEdit;
this.initForm();
this.dialogService.open(this.editDialog, {});
}
private initForm() {
const today: Date = new Date();
this.bookingForm = this.formBuilder.group({
description: [this.booking.description, [Validators.required, MyValidators.nospaceValidator]],
amount: [this.booking.amount, [Validators.required, MyValidators.validateAmount]],
date: [today, Validators.required],
capitalSource: [this.booking.capitalSource.id, Validators.required],
category: [this.booking.subcategory.id, Validators.required],
contractpartner: [this.booking.contractPartner.name, [Validators.required, MyValidators.nospaceValidator]],
privatCheckbox: [this.booking.privatBookingOfUser],
});
}
}
子对话框模板
<toaster-container></toaster-container>
<ng-template #dialogedit let-data let-ref="dialogRef">
<form [formGroup]="bookingForm">
<nb-card>
<nb-card-header>Buchung bearbeiten</nb-card-header>
<nb-card-body>
<div class="row">
<div class="col-md-3">
<div class="form-group">
<div class="input-group input-group-sm">
<input nbInput fullWidth
placeholder="Buchungsdatum"
formControlName="date"
style="line-height: 1rem;"
[ngClass]="{
'status-danger': bookingForm.controls.date.invalid && bookingForm.controls.date.dirty,
'status-success': bookingForm.controls.date.valid && bookingForm.controls.date.dirty }"
[nbDatepicker]="formpicker">
<nb-datepicker #formpicker></nb-datepicker>
</div>
</div>
</div>
<div class="col-md-6">
<div class="form-group input-group-sm">
<input [ngClass]="{
'status-danger': bookingForm.controls.description.invalid && bookingForm.controls.description.dirty,
'status-success': bookingForm.controls.description.valid && bookingForm.controls.description.dirty }"
class="status" fullWidth id="description" nbInput placeholder="Beschreibung"
formControlName="description" type="text"/>
</div>
</div>
<div class="col-md-2">
<div class="form-group input-group-sm">
<input [ngClass]="{
'status-danger': bookingForm.controls.amount.invalid && bookingForm.controls.amount.dirty,
'status-success': bookingForm.controls.amount.valid && bookingForm.controls.amount.dirty }"
class="status" fullWidth nbInput step="0.10" type="number"
formControlName="amount"
placeholder="Betrag" type="text"/>
</div>
</div>
</div>
<div class="row">
<div class="col-md-5">
<div class="form-group input-group-sm">
<input nbInput fullWidth id="typeahead-format" placeholder="Vertragspartner"
formControlName="contractpartner"
type="text" placeholder="Vertragspartner"
[ngClass]="{
'newContractPartner': newContractPartner,
'status-danger': bookingForm.controls.contractpartner.invalid && bookingForm.controls.contractpartner.dirty,
'status-success': bookingForm.controls.contractpartner.valid && bookingForm.controls.contractpartner.dirty }"
[ngbTypeahead]="searchContractPartner" class="status"/>
</div>
</div>
<div class="col-md-4">
<div class="form-group input-group-sm">
<nb-select nbSelect fullWidth [size]="formSize"
formControlName="category" placeholder="Kategorie"
[ngClass]="{
'status-danger': bookingForm.controls.category.invalid && bookingForm.controls.category.dirty,
'status-success': bookingForm.controls.category.valid && bookingForm.controls.category.dirty }">
<option disabled [value]=null>Kategorie</option>
<nb-option-group *ngFor="let category of initialData?.categories" title="{{category.name}}">
<nb-option *ngFor="let subcategory of category.subcategories" [value]="subcategory.id">
{{subcategory.name}}
</nb-option>
</nb-option-group>
</nb-select>
</div>
</div>
<div class="col-md-3">
<div class="form-group input-group-sm">
<nb-select fullWidth [size]="formSize"
formControlName="capitalSource"
[ngClass]="{
'status-danger': bookingForm.controls.capitalSource.invalid && bookingForm.controls.capitalSource.dirty,
'status-success': bookingForm.controls.capitalSource.valid && bookingForm.controls.capitalSource.dirty }">
<nb-option disabled [value]=null>Kapitalquelle</nb-option>
<nb-option *ngFor="let capitalSource of initialData?.capitalSources" [value]="capitalSource.id">
{{capitalSource.description}}
</nb-option>
</nb-select>
</div>
</div>
<div class="col-md-2">
<div class="form-group input-group-sm" style="color: #a1a1e5 !important;">
<nb-checkbox formControlName="privatCheckbox">Privat</nb-checkbox>
</div>
</div>
</div>
</nb-card-body>
<nb-card-footer>
<div class="col-md-12">
<div class="form-group input-group-sm">
<div _ngcontent-c40="" class="container-btn">
<button [disabled]="!bookingForm.valid" nbButton>
Buchung speichern
</button>
</div>
</div>
</div>
</nb-card-footer>
</nb-card>
</form>
</ng-template>
我尝试在 setTimeout 中调用 dialog.open 方法,并尝试在 resolvedPromise.then(() => {...} 中预填充表单数据,但没有成功。
有人知道问题出在哪里吗?
StackBlitz:stackblitz.com/edit/github-lv7hp3
运行 change detection
显式更改后。请在您的组件中添加这段代码,这应该可以解决您的问题。
import { ChangeDetectorRef } from '@angular/core';
constructor(private cdRef:ChangeDetectorRef) {}
ngAfterViewChecked()
{
this.cdRef.detectChanges();
}
Angular 文档 link => https://angular.io/api/core/ChangeDetectionStrategy
GitHub 问题 => https://github.com/angular/angular/issues/15634