У меня есть огромная форма, которую я разбил на мелкие компоненты размером с укус. Я делаю это, передавая свою форму компоненту и имея этот компонент t ie в своих собственных полях для формы, например, так:
Основная форма
<h3>Not previously treated with</h3>
<app-treatment-group [form]="form.get('patients')" [formName]="'notPreviouslyTreated'"></app-treatment-group>
1-й слой вложенности (группа обработки)
<ng-container [formGroup]="form">
<div class="questionGroup multipliable vertical hasMultiplicationText" [formArrayName]="formName"
*ngFor="let outerTreatment of form.get(formName)?.controls; let i = index; let lastOuter = last">
<app-multiplication-buttons
[elementCount]="form.get(formName)?.controls.length"
(add)="addTreatment()"
(remove)="removeTreatment(i)"
[tooltipText]="'Add another group'"></app-multiplication-buttons>
<app-treatment [form]="outerTreatment"></app-treatment>
<h4 *ngIf="!lastOuter" class="multiplicationText">Or</h4>
</div>
</ng-container>
@Component({
selector: 'app-treatment-group',
templateUrl: './treatment-group.component.html',
styleUrls: ['./treatment-group.component.css']
})
export class TreatmentGroupComponent implements OnInit {
@Input() public form: FormGroup;
@Input() public formName: string;
private subscriptions: { [key: string]: Subscription } = {};
constructor(
private formBuilder: FormBuilder
) {
}
ngOnInit() {
// We need to add controls asynchronously to avoid ExpressionChangedAfterItHasBeenCheckedError errors
// https://stackoverflow.com/questions/45661010/dynamic-nested-reactive-form-expressionchangedafterithasbeencheckederror
Promise.resolve(null).then(() => {
this.createForm();
this.subscriptions.formValueChanges = this.form.get(this.formName).valueChanges.subscribe((value) => {
console.log(value);
});
});
}
private createForm() {
this.form.addControl(this.formName, this.formBuilder.array([this.creatTreatment()
]));
}
private creatTreatment () {
return this.formBuilder.group({});
}
private addTreatment() {
(this.form.get(this.formName) as FormArray).push(this.creatTreatment());
}
public removeTreatment(i: number): void {
(this.form.get(this.formName) as FormArray).removeAt(i);
}
}
2-й слой вложенности (обработка)
<ng-container [formGroup]="form" *ngIf="shouldDisplay">
<div class="questionGroup multipliable"
formArrayName="treatmentGroup"
*ngFor="let innerTreatment of form.get('treatmentGroup')['controls']; let i = index">
<ng-container [formArrayName]="i">
<div class="question">
<app-mapping-input
formControlName="treatment"
[label]="labels.name"
[requiredIndicator]="shouldBeRequired"
[usage]="'drug'"></app-mapping-input> <!-- something with a Control Value Accessor -->
<app-error [control]="innerTreatment.get('treatment')"></app-error>
</div>
<div class="question">
<label for="treatmentDetails-{{i}}-{{uniqueIdentifier}}">{{labels.details}}</label>
<input type="text" id="treatmentDetails-{{i}}-{{uniqueIdentifier}}"
formControlName="treatmentDetails">
</div>
<app-multiplication-buttons
[elementCount]="form.get('treatmentGroup')['controls'].length"
(add)="addTreatmentInner()"
(remove)="removeTreatmentInner(i)"
[tooltipText]="'Add another treatment'">
</app-multiplication-buttons>
</ng-container>
</div>
</ng-container>
@Component({
selector: 'app-treatment',
templateUrl: './treatment.component.html',
styleUrls: ['./treatment.component.css', './../../curation-common.css']
})
export class TreatmentComponent implements OnInit, OnDestroy, AfterViewInit {
@Input() form: FormGroup;
@Input() labels = {
name: 'Treatment name',
details: 'Treatment details',
};
@Input() shouldBeRequired = false; // WARNING: Do *NOT* rename this property to required, it will cause aExpressionChangedAfterItHasBeenCheckedError
public uniqueIdentifier = Math.floor(Math.random() * 1000000);
public subscriptions = {};
public shouldDisplay = false;
constructor(private formBuilder: FormBuilder) {this.setRequiredValidator();}
ngOnInit() {
Promise.resolve(null).then(() => {
this.createForm();
this.shouldDisplay = true;
});
}
ngOnDestroy() {CommonService.unsubscribeFromAll(this.subscriptions);}
public addTreatmentInner(): void {
(this.form.get('treatmentGroup') as FormArray).push(this.createTreatmentInner());
}
public removeTreatmentInner(i: number): void {
(this.form.get('treatmentGroup') as FormArray).removeAt(i);
}
private createForm() {
this.form.addControl('treatmentGroup', this.formBuilder.array([this.createTreatmentInner()]));
}
private createTreatmentInner(): FormGroup {
return this.formBuilder.group({
treatment: ['', [mappedValidator()]],
treatmentDetails: ['']
});
}
private setRequiredValidator() {
if (this.shouldBeRequired) {
this.form.setValidators([Validators.required]);
this.form.updateValueAndValidity();
}
}
}
Проблема
Теперь проблема в том, что, когда я получаю вложенный объект от серверной части (под нагрузкой), мой массив не будет добавлять или удалять элементы, когда я исправляю форму со значением. Я понимаю, что это нормальное поведение, но, с другой стороны, я не вижу способа добавлять или удалять элементы формы без тесного связывания основной формы с дочерней формой.
То, что я считал
Я подумал о том, чтобы просто указать количество элементов массива форм app-treatment-group
, и его дочерний элемент должен быть получен через @Input()
, но это обходной путь, а не правильное решение.
Я также подумал, что могу просто подписаться если значение формы изменяется, перехватывает объект и создает или удаляет элементы массива формы по мере необходимости, но при изменении значения я просто получаю усеченный объект.
Хотя однокомпонентные формы довольно просты и будут безусловно, решит мои проблемы, эта опция неосуществима, так как я работаю со многими различными формами, каждая из которых состоит из набора компонентов с одинаковыми полями и обрабатывает эти формы и приведет к большому количеству повторений кода. Поэтому я ищу решение, которое работает, будучи инкапсулированным в его собственный компонент или с минимальной связью между ним и его родителем.