РЕДАКТИРОВАТЬ: мне удалось добиться успешного прохождения и проверки данных, переписав email-phone-input.component.ts, чтобы использовать ControlContainer, получить FormGroup от родителя вместе с элементами управления FormArray. Обновит репо с рабочим кодом и ответит на вопрос.
Я пытаюсь заставить работать валидаторы для группы форм, содержащей объекты, отправленные из дочернего компонента с массивами форм.
Текущая иерархия - индивидуальная электронная почта компонент, отдельный компонент телефона (который использует внешний пакет), затем компонент «электронная почта + телефон», содержащий FormArray для электронной почты и телефона, а затем мастер-форму с единым управлением формы, которая получает данные из компонента «электронная почта + телефон».
У меня есть данные, проходящие весь путь, но я не могу понять, как получить валидаторы в основную форму.
Ссылка на stackblitz , содержащий код + демо https://stackblitz.com/github/rushvora/nested-form-playground
Примечание: Validators.required
не работает для ввода электронной почты при добавлении нового элемента управления формы в массив форм.
app.component.ts
import { Component, OnInit } from '@angular/core';
import { FormGroup, FormBuilder, Validators } from '@angular/forms';
@Component({
selector: 'app-root',
templateUrl: './app.component.html',
styleUrls: ['./app.component.css']
})
export class AppComponent implements OnInit {
title = 'nested-form-playground';
form: FormGroup;
emailsAndPhones: FormGroup;
constructor(private fb: FormBuilder) { }
ngOnInit() {
this.form = this.fb.group({
notifications: this.fb.group({
emailsAndPhones: []
})
});
this.emailsAndPhones = this.form.get('notifications.emailsAndPhones') as FormGroup;
}
submit() {
console.log(this.form.valid, this.form.status);
}
}
app.component. html
<div class="container-fluid mt-3">
<div class="card">
<form [formGroup]="form" (ngSubmit)="submit()">
<div class="card-header bg-light">
<h4>Nested Form Testing.</h4>
</div>
<div class="card-body" formGroupName="notifications">
<app-email-phone-input formControlName="emailsAndPhones"></app-email-phone-input>
</div>
<div class="card-footer bg-light">
<button>Submit</button>
</div>
</form>
</div>
</div>
email-phone-input.component. ts
import { Component, OnInit, OnDestroy, forwardRef } from '@angular/core';
import {
FormGroup, FormBuilder, FormArray, FormControl, ControlValueAccessor,
Validator, NG_VALUE_ACCESSOR, NG_VALIDATORS, Validators, AbstractControl
} from '@angular/forms';
import { Subject } from 'rxjs';
import { takeUntil } from 'rxjs/operators';
@Component({
selector: 'app-email-phone-input',
templateUrl: './email-phone-input.component.html',
styleUrls: ['./email-phone-input.component.css'],
providers: [
{
provide: NG_VALUE_ACCESSOR,
useExisting: forwardRef(() => EmailPhoneInputComponent),
multi: true
},
{
provide: NG_VALIDATORS,
useExisting: forwardRef(() => EmailPhoneInputComponent),
multi: true
}
]
})
export class EmailPhoneInputComponent implements OnInit, ControlValueAccessor, Validator, OnDestroy {
emailsAndPhonesForm: FormGroup;
emails: FormArray;
phones: FormArray;
private destroy$ = new Subject();
constructor(private fb: FormBuilder) { }
ngOnInit() {
this.emailsAndPhonesForm = this.fb.group({
emails: this.fb.array([]),
phones: this.fb.array([])
});
this.emails = this.emailsAndPhonesForm.get('emails') as FormArray;
this.phones = this.emailsAndPhonesForm.get('phones') as FormArray;
}
public onTouched: () => void = () => { };
writeValue(val: any): void {
val && this.emailsAndPhonesForm.setValue(val, { emitEvent: false });
}
registerOnChange(fn: any): void {
this.emailsAndPhonesForm.valueChanges
.pipe(takeUntil(this.destroy$))
.subscribe(fn);
}
registerOnTouched(fn: any): void {
this.onTouched = fn;
}
validate(_: AbstractControl) {
let emailsValidity = {};
let phonesValidity = {};
this.emails.valid ? emailsValidity = null : { invalidForm: {valid: false, message: `Email is invalid.`}};
this.phones.valid ? phonesValidity = null : { invalidForm: {valid: false, message: `Phone is invalid.`}};
console.log('Emails and Phones Form in validate: ');
console.log(this.emailsAndPhonesForm);
console.log('Emails in validate: ');
console.log(this.emails);
console.log('Phones in validate: ');
console.log(this.phones);
if (emailsValidity && phonesValidity) {
const combinedValidity = { invalidForm: {valid: false, message: `Email & phone are invalid.`}};
return combinedValidity;
} else if (emailsValidity) {
return emailsValidity;
} else if (phonesValidity) {
return phonesValidity;
} else {
return null;
}
}
addEmail() {
this.emails.push(new FormControl('', Validators.email));
}
addPhone() {
this.phones.push(new FormControl('', Validators.required));
console.log('Emails and Phones Form in addPhone: ');
console.log(this.emailsAndPhonesForm);
}
ngOnDestroy() {
this.destroy$.next();
this.destroy$.complete();
}
}
email-phone-input.component. html
<!-- Array of email inputs -->
<ng-container [formGroup]="emailsAndPhonesForm">
<ng-container formArrayName="emails">
<ng-container *ngFor="let email of emails.controls; index as i">
<div class="row">
<app-email-input [index]="i+1" [formControlName]="i"></app-email-input>
</div>
</ng-container>
<button type="button" (click)="addEmail()">Another Email</button>
</ng-container>
<ng-container formArrayName="phones">
<ng-container *ngFor="let phone of phones.controls; index as i">
<div class="row">
<div class="form-group">
<label>Phone {{ i + 1 }}</label>
<app-phone-input [formControlNameCustom]="i"></app-phone-input>
</div>
</div>
</ng-container>
<button type="button" (click)="addPhone()">Another Phone</button>
</ng-container>
</ng-container>
<!-- Array of phone inputs -->
<div class="jumbotron">
<div *ngFor="let email of emails.value">
{{email?.email }}
</div>
<div *ngFor="let phone of phones.value">
{{phone?.internationalNumber }}
</div>
</div>