65

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?

3 Answers 3

97

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
    ]));
Sign up to request clarification or add additional context in comments.

3 Comments

Thanks for that nice solution! One question: What's the difference between Validators.compose([ // myValidators ]) and just passing the validators as an array as a third parameter without Validators.compose()?
There isn't really a difference. As far as I remember, just passing them was not yet supported when I posted this answer (at least not for async validators).
The issues regarding combinaison of sync and async validators seems to be resolved!
72

this problem has been addressed

you can make an array of validators

this.username = new FormControl('', [ Validators.minLength(5), Validators.required ]); 

1 Comment

What is the difference with using compose ?
15

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:

  1. formState (or simply the initial starting value)
  2. validators (I suggest using Validators.compose([...]) here)
  3. asyncValidators (I suggest using Validators.composeAsync([...]) here)

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.

1 Comment

Thanks. I suppose one should return the Promise.resolve(null) in case of empty input.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.