Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

General: asynchonious validation in angular2

Since couple evening I've played with form validation in augular2.

All basic cases were easy to implement and they works fine but I stick with asynchronous validation. I have created a very tiny example http://plnkr.co/edit/Xo8xwJjhlHkXrunzS8ZE and it didn't work.

According to test "should fire an event after the status has been updated to pending" from model_spec.ts Registration via creation of control group suppose to work in a way

builder.group({login: ["",Validators.required,validationFuctionWhichReturnsPromise]

I spent a full evening to discovered that this code has been released in alfa-46 (and I used alfa-45) and after update depencies the async validation started to work. The feature is very fresh and is not fully documented but

(for those who haven't tried it yet) Basically async validator is a function which have a Control argument and return a promise which validation result. There are two ways to register a validator. 1) the one which I used in my example and 2) as a directive which Provide validators via NG_ASYNC_VALIDATORS (See UniqLoginValidator and NgFormControl to see how it work). You can compose more than one validator (not tested yet but functions to do this are in code, see https://github.com/angular/angular/commit/cf449dd).

But when I finally reach to up and running validators a new problem arrived. Async validator is perfect to used it in server side validation. But the validation is invoked after each change of model.fe after each keyup. So if we will send request to a server after each key up, it won't be too efficient way ;) I checked how it is done in angular 1 and they is a possibility to debounce validation events.

My questions are:

  1. How to implement throttle or debounce with async validators? I saw some ideas but none of them were fine (mostly because they need to change angular code itself). Is there any valid way to do this without waiting for new angular release ?

I was thinking about to warping a validator function with debounce (from underscorejs) but it will not work because angular expects to get a valid promise every time.

My second though was that if all event use RxJs under the hood then maybe I can apply debounce on stream of event which is responsible for validation. In model.ts the promise returned from validator is change to observable and a new subscribed is added. We don't have any access to obs(Observable) to apply debounce there.

  1. Is there any way or id to change,easy extend a control over the form validation ?

I spotted a close related problem in How to trigger Form Validators in angular2

PS there is other issue related to async validators and it is still open https://github.com/angular/angular/issues/1068

like image 342
przemcio Avatar asked Dec 18 '22 22:12

przemcio


1 Answers

Here is a helper class that you can use to debounce all your async validators:

import {Component} from 'angular2/core';
import {Observable} from 'rxjs/Observable';
import {Observer} from 'rxjs/Observer';
import 'rxjs/add/operator/debounceTime';
import 'rxjs/add/operator/distinctUntilChanged';
import {Control} from 'angular2/common';

export class AsyncValidator {
_validate;

constructor(validator: (control: Control) => any, debounceTime = 1000) {
    let source: any = new Observable((observer: Observer<Control>) => {
        this._validate = (control) => observer.next(control);
    });

    source.debounceTime(debounceTime)
        .distinctUntilChanged(null, (x) => x.control.value)
        .map(x => { return { promise: validator(x.control), resolver: x.promiseResolver }; })
        .subscribe(
            (x) => x.promise.then(resultValue => x.resolver(resultValue),
            (e) => { console.log('async validator error: %s', e); }));
}

private _getValidator() {
    return (control) => {
        let promiseResolver;
        let p = new Promise((resolve) => {
            promiseResolver = resolve;
        });
        this._validate({ control: control, promiseResolver: promiseResolver });
        return p;
    };
}

static debounce(validator: (control: Control) => any, debounceTime = 400) {
    var asyncValidator = new this(validator, debounceTime);
    return asyncValidator._getValidator();
}
}

Then all you have to do where use async validators is just wrap your validator with this call and write your validator the same as you would normally:

AsyncValidator.debounce(control => this.asyncValidator(control));

Here is an example usage:

export class AppComponent {
form: ControlGroup;

constructor(private _formBuilder: FormBuilder) {
    var validator = AsyncValidator.debounce(control => this.asyncValidator(control));

    this.form = _formBuilder.group({
        name: ['', Validators.required, validator],
    });
}

asyncValidator(control): any {
    let p = new Promise(resolve => {
        // get from server information need to validate control

        if (control.value === 'valid value') {
            resolve(null);
        } else {

            resolve({
                asyncValidator: {
                    valid: false
                }
            });
        }
    });
    return p;
}
}
like image 82
Jami Lurock Avatar answered Jan 15 '23 13:01

Jami Lurock