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...
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
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
}
}
}