#angular #angular-reactive-forms #angular-forms
Вопрос:
Версия : Угловой 12.2 Стекблитц : https://stackblitz.com/edit/angular-ivy-r8cpbh?file=src/app/app.component.html
Привет, у меня есть проект с 3 компонентами типа :
- Родитель со списком детей
- Ребенок со списком дочерних детей
- Дочерний автономный компонент
На родительском уровне мы можем добавить ребенка
На уровне ребенка мы можем добавить дочерний элемент
Все работает нормально, но когда я добавляю новую строку, иногда (при изменении допустимого поля) у меня появляется это сообщение в FormGroup.недопустимо :
NG0100: ExpressionChangedAfterItHasBeenCheckedError: Выражение изменилось после проверки. Предыдущее значение для «отключено»: «ложь». Текущее значение: «истина»
Я знаю, что это распространенная ошибка, но на этот раз я не нашел, откуда берутся ошибки…
Пожалуйста, помогите.
Parent
|____Child
|______SubChild
|______SubChild
|______SubChild
|____Child
|______SubChild
|____Child
|______SubChild
|______SubChild
Родительские ts
public data!: ParentData;
public formGroup!: FormGroup;
get children() {
return <FormArray>this.formGroup.controls.children;
}
constructor(private fb: FormBuilder) {}
ngOnInit() {
this.data = {
parentField1: 'string',
parentField2: 'string',
parentHiddenField1: 'string',
children: []
};
this.formGroup = this.toFormGroup(this.data);
this.formGroup.valueChanges.subscribe(value => console.log(value));
}
add() {
this.children.push(new FormGroup({}));
this.data.children.push({
id: 1,
childField1: 'string',
subchildren: []
});
}
private toFormGroup(data: ParentData): FormGroup {
const formGroup = this.fb.group({
parentField1: [data.parentField1, Validators.required],
parentField2: [data.parentField2, Validators.required],
parentHiddenField1: [data.parentHiddenField1],
children: new FormArray([])
});
return formGroup;
}
submit(finalData: ParentData) {
console.log(finalData);
}
}
Parent Html
<form [formGroup]="formGroup" (ngSubmit)="submit(formGroup.value)">
<label for="parentField1">Parent Field 1</label>
<input formControlName="parentField1" />
<br />
<label for="parentField2">Parent Field 2</label>
<input formControlName="parentField2" />
<div formArrayName="children">
<button type="button" (click)="add()"> </button>
<div *ngFor="let child of data.children; let i=index">
<div>Parent</div>
<app-test-list-child [formGroup]="children.controls[i] | formGroup" [data]="child">
</app-test-list-child>
</div>
</div>
<button type="submit" [disabled]="formGroup.invalid">Send</button>
</form>
Child ts
export class TestListChildComponent implements OnInit, AfterViewInit {
@Input('formGroup')
public formGroup!: FormGroup;
@Input('data')
public data!: ChildData;
get subchildren() {
return <FormArray>this.formGroup.get('subchildren');
}
constructor(private cd: ChangeDetectorRef) {}
ngOnInit() {
this.formGroup.addControl(
'childField1',
new FormControl(this.data.childField1 || '', Validators.required)
);
this.formGroup.addControl('subchildren', new FormArray([]));
}
ngAfterViewInit() {}
add() {
this.subchildren.push(new FormGroup({}));
this.data.subchildren.push({
childField1: 'test',
childHiddenField1: '',
childField2: '',
id: 1
});
}
}
Child html
<div [formGroup]="formGroup">
<input formControlName="childField1" />
<button type="button" (click)="add()"> </button>
<div formArrayName="subchildren">
<div *ngFor="let child of subchildren.controls; let i = index">
<div>Child</div>
<app-test-child [formGroup]="child | formGroup" [data]="data.subchildren[i]">
</app-test-child>
</div>
</div>
</div>
SubChild ts
export class TestChildComponent implements OnInit, AfterViewInit {
@Input('formGroup')
public formGroup!: FormGroup;
@Input('data')
public data!: SubChildData;
constructor(private fb: FormBuilder) { }
ngOnInit() {
this.toFormGroup(this.data);
}
ngAfterViewInit() {
}
private toFormGroup(data: SubChildData) {
this.formGroup.addControl("childField1", new FormControl(data?.childField1 || '', Validators.required));
this.formGroup.addControl("childField2", new FormControl(data?.childField2 || '', Validators.required));
}
}
Дочерний html
<!-- child-form.component.html -->
SubChild
<div [formGroup]="formGroup">
<label for="childField1">Child Field 1</label>
<input formControlName="childField1" />
<br/>
<label for="childField1">Child Field 2</label>
<input formControlName="childField2" />
</div>
Модель
export interface ParentData {
parentField1: string;
parentField2: string;
parentHiddenField1: string;
children: ChildData[];
}
export interface ChildData {
id: number;
childField1: string;
subchildren: SubChildData[];
}
export interface SubChildData {
id: number;
childField1: string;
childField2: string;
childHiddenField1: string;
}
Труба
@Pipe({
name: 'formGroup'
})
export class FormGroupPipe implements PipeTransform {
transform(value: AbstractControl, ...args: unknown[]): FormGroup {
return value as FormGroup;
}
}
Ответ №1:
Добавление this.cdr.detectChanges()
в конце add
дочернего метода » » исправит это.
Комментарии:
1. Thx, но это добавляет новое сообщение об ошибке : NG0303: Не удается привязаться к «NgForOf», так как это неизвестное свойство «div».
2. Мой плохой, это была ошибка стекблитца, ваше изменение заставило его работать очень много, не могли бы вы объяснить мне, что делает this.cd.DetectChanges(); это не повлияло на значение и ценность обновления, пожалуйста ?
3. AFAIK, это не рекомендуемое решение. Подробнее здесь: подробно .dev/сообщения/1001/.. …
Ответ №2:
Ошибка возникает из-за того, что в test-child-component.ts у вас есть необходимый валидатор:
private toFormGroup(data: SubChildData) {
this.formGroup.addControl("childField1", new FormControl(data?.childField1 || '', Validators.required));
this.formGroup.addControl("childField2", new FormControl(data?.childField2 || '', Validators.required));
}
…но в test-list-component.ts вы не назначаете значение для элемента управления childField2, и форма становится недействительной.
Самым простым решением было бы назначить значение по умолчанию для второго элемента управления:
add() {
this.subchildren.push(new FormGroup({}));
this.data.subchildren.push({
childField1: 'test',
childHiddenField1: '',
childField2: 'default value',
id: 1
});
}
Комментарии:
1. Thx для вашего ответа, но эти поля должны быть «Пустыми» в начале.
Ответ №3:
В конце концов я обнаружил, что pb был своего рода асинхронным шаблоном …
Я добавляю параметр setTimeout для создания форм, и теперь все работает хорошо.
Ребенок
setTimeout(() => {
this.formGroup.addControl(
'childField1',
new FormControl(this.data.childField1 || '', Validators.required)
);
this.formGroup.addControl('subchildren', new FormArray([]));
});
Дочерний элемент
setTimeout(() => {
this.formGroup.addControl("childField1", new FormControl(data?.childField1 || '', Validators.required));
this.formGroup.addControl("childField2", new FormControl(data?.childField2 || '', Validators.required));
});