Sebastian Olsen
Sebastian Olsen

Reputation: 10878

Angular 2 Multiple validators

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?

Upvotes: 65

Views: 77547

Answers (3)

Ankit Raonka
Ankit Raonka

Reputation: 6839

this problem has been addressed

you can make an array of validators

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

Upvotes: 71

Grey Perez
Grey Perez

Reputation: 20438

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.

Upvotes: 14

G&#252;nter Z&#246;chbauer
G&#252;nter Z&#246;chbauer

Reputation: 657068

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
    ]));

Upvotes: 96

Related Questions