How to trigger child component validators from par

2020-08-01 07:05发布

问题:

I'm having trouble with conditional validation using template forms in Angular. I've created a custom EmailInputComponent:

<div class="form-group" provide-parent-form>
  <label for="email">Email<span *ngIf="required">*</span></label>
  <input id="email"
         class="form-control"
         name="email"
         type="email"
         [(ngModel)]="emailAddress"
         #email="ngModel"
         maxlength="255"
         validateEmail
         [required]="required ? '' : null"/>
  <error [model]="email" [referencedValue]="emailAddress"></error>
</div>

which is hosted inside a parent MyFormComponent:

<form #form="ngForm" name="form" (ngSubmit)="onSubmit($event)">
  <fieldset>
    <email [(emailAddress)]="model.email" [required]="emailRequired()"></email> 
    <!-- select component here -->
  </fieldset>    
  <button type="submit" [disabled]="!form.form.valid">Send</button>
</form>

The form also contains a SelectComponent where users can choose their preferred way of contact. If users select "email", the email input becomes mandatory.

As you can, see there is some logic going on in the parents emailRequired function that dynamically calculates whether an email input is mandatory or not based on the currently selected preferred way of contact. Whenever this selected value changes I need to somehow trigger the email input validators. How can I do that?

Using @ViewChild I managed to get a hold of the EmailInputComponent from the MyFormComponent. But I don't know how to proceed now...

回答1:

I don't think you need to specify attr in front to refer to the required property.

Try the simple,

 [required]="required ? '' : null"

And if it still didn't work, it is likely because of the '@Input' required property not getting updated in the EmailInputComponent. So as @naeramarth7 suggested look for it in the ngOnChanges hook of the EmailInputComponent and update.

ngOnChanges(changes: SimpleChange) {
 for (let prop in changes) {
  if(prop === 'required') { 
   this.required = changes[prop].currentValue;
  }
 }
}

make sure, when you use it, to add implements OnChanges to the EmailInputComponent declaration and also to import SimpleChange and OnChanges from '@angular/core' inside the email-input.component.ts



回答2:

Meanwhile I found a solution. My child component now references the according FormGroup.

<email [(emailAddress)]="model.email" 
       [formGroup]="form.form" 
       [required]="emailRequired()">
</email>

Via the FormGroup, you can then reach to the child components actual FormControl and call updateValueAndValidity inside the setter of the validator flag (set required in my case):

import {Component, EventEmitter, Input, Output} from '@angular/core';
import {FormGroup} from '@angular/forms';

@Component({
  selector: 'email',
  templateUrl: './email.component.html',
  styleUrls: ['./email.component.css']
})
export class EmailInputComponent {

  private _emailAddress: string;

  public _required = true;

  @Input()
  private formGroup: FormGroup;

  @Output()
  emailAddressChange = new EventEmitter();

  constructor() { }

  @Input()
  get emailAddress(): string {
    return this._emailAddress;
  }

  set emailAddress(value: string) {
    this._emailAddress = value;
    this.emailAddressChange.emit(this._emailAddress);
  }

  @Input()
  get required(): boolean {
    return this._required;
  }

  set required(value: boolean) {
    this._required = value;
    // this is where the magic happens
    const refEmailControl = this.formGroup.controls.email;
    if (refEmailControl) {
      refEmailControl.updateValueAndValidity(); // updates the validity state      
      refEmailControl.markAsTouched(); // necessary in my case to make UI render error message
    }
  }
}