Распространение валидаторов FormArray от дочернего компонента к родительскому компоненту в Angular 8 - PullRequest
1 голос
/ 30 января 2020

РЕДАКТИРОВАТЬ: мне удалось добиться успешного прохождения и проверки данных, переписав 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>

1 Ответ

0 голосов
/ 04 февраля 2020

Я исправил проблему, используя ControlContainer для получения родительской группы форм из родительского / основного компонента. Таким образом, я смог обновить достоверность, установить валидаторы в дочернем компоненте для родительских элементов управления формы напрямую. Базовые компоненты все еще должны быть реализованы с использованием CVA, здесь вход электронной почты и сторонний телефонный пакет действительно используют CVA для реализации своих элементов управления, но слоям над ними вплоть до верхнего уровня просто нужно использовать ControlContainer, чтобы передать основную форму группы / элементы управления между компонентами.

Благодаря этому ответу за приведение меня к решению - Создание многоразовой группы FormGroup

Обновленный код - https://github.com/rushvora/nested-form-playground

...