Is it possible to have multiple validators on a form field? I tried this, but it resulted in some strange errors (field was never valid, even if requirements were met)
this.username = new Control('', Validators.minLength(5), Validators.required);
How can I use multiple validators?
You can combine validators using Validators.compose()
this.username = new Control('',
Validators.compose(
[Validators.minLength(5), Validators.required]));
for async validators use
this.username = new Control('', null,
Validators.composeAsync(
[someAsyncValidator, otherAsyncValidator]));
There are open issues with async validators, especially sync validators combined with async validators don't work
To make sync validators work with async validators, wrap the sync validators in promises and compose them as async valdiators like
this.username = new Control('', null,
Validators.composeAsync([
(control:Control) => Promise.resolve(Validators.minLength(5)(control)),
(control:Control) => Promise.resolve(Validators.required(control)),
someAsyncValidator, otherAsyncValidator
]));
this problem has been addressed
you can make an array of validators
this.username = new FormControl('', [ Validators.minLength(5), Validators.required ]);
I suggest utilizing the Validators.compose() method for combining all non-async validators and separately passing in the Validators.composeAsync() for any async calls.
Basically the constructor args for FormControl are as follows:
Example using FormBuilder (feel free to use straight up Control):
this.acctForm = this.fb.group({
'name': [
'',
Validators.compose([
Validators.required, Validators.minLength(2), Validators.maxLength(20), Validators.pattern('[a-zA-Z]')
])
],
'cellNumber': [
'',
Validators.compose([
Validators.required, Validators.pattern('[0-9]{10}')
]),
Validators.composeAsync([
this.checkPhoneValid.bind(this)
])
]
});
This helps avoid async validation until the non-async validators are valid (excl. the initial check, which can easily be handled, see further below).
Everything Combined Example (validators, asyncValidators & debouncing):
import { Component, Injectable, OnInit } from '@angular/core';
import { Http } from '@angular/http';
import { FormBuilder, FormGroup, Validators, AbstractControl } from '@angular/forms';
@Component({
selector: 'app-sandbox',
templateUrl: './sandbox.component.html',
providers: []
})
export class FormControlsDemoComponent implements OnInit {
private debouncedTimeout;
public acctForm: FormGroup;
constructor(private http: Http, private fb: FormBuilder) {
// @note Http should never be directly injected into a component, for simplified demo sake...
}
ngOnInit() {
this.acctForm = this.fb.group({
// Simple Example with Multiple Validators (non-async)
'name': [
'',
Validators.compose([
Validators.required, Validators.minLength(2), Validators.maxLength(20), Validators.pattern('[a-zA-Z]')
])
],
// Example which utilizes both Standard Validators with an Async Validator
'cellNumber': [
'',
Validators.compose([
Validators.required, Validators.minLength(4), Validators.maxLength(15), Validators.pattern('[0-9]{10}')
]),
Validators.composeAsync([
this.checkPhoneValid.bind(this) // Important to bind 'this' (otherwise local member context is lost)
/*
@note if using a service method, it would look something like this...
@example:
this.myValidatorService.phoneUniq.bind(this.myValidatorService)
*/
])
],
// Example with both, but Async is implicitly Debounced
'userName': [
'',
Validators.compose([
Validators.required, Validators.minLength(4), Validators.maxLength(15), Validators.pattern('[a-zA-Z0-9_-]')
]),
Validators.composeAsync([
this.checkUserUniq.bind(this) // @see above async validator notes regarding use of bind
])
]
});
}
/**
* Demo AsyncValidator Method
* @note - This should be in a service
*/
private checkPhoneValid(control: AbstractControl): Promise<any> {
// Avoids initial check against an empty string
if (!control.value.length) {
Promise.resolve(null);
}
const q = new Promise((resolve, reject) => {
// determine result from an http response or something...
let result = true;
if (result) {
resolve(null);
} else {
resolve({'phoneValidCheck': false});
}
});
return q;
}
/**
* Demo AsyncValidator Method (Debounced)
* @note - This should be in a service
*/
private checkUserUniq(control: AbstractControl): Promise<any> {
// Avoids initial check against an empty string
if (!control.value.length) {
Promise.resolve(null);
}
clearTimeout(this.debouncedTimeout);
const q = new Promise((resolve, reject) => {
this.debouncedTimeout = setTimeout(() => {
const req = this.http
.post('/some/endpoint', { check: control.value })
.map(res => {
// some handler logic...
return res;
});
req.subscribe(isUniq => {
if (isUniq) {
resolve(null);
} else {
resolve({'usernameUnique': false });
}
});
}, 300);
});
return q;
}
}
Some people like to hack in debounced async validator by binding to the control's Observable valueChanges like this:
this.someControl.debounceTime(300).subscribe(val => {
// async call...
});
I personally don't recommend this for most cases, as it adds unnecessary complications.
NOTE: This should work, as of the latest version of Angular (2 & 4) since the writing of this post.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With