Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Async form validation with debounce?

I am wondering how can I implement the debounce time on my async validator.

I have the following:

...
password: ['',Validators.compose([Validators.required, this.passwordValid])]
...

Where:

passwordValid(control:Control):{ [key: string]: any; } {
    return new Promise(resolve => {
        this._http.post('/passwordCheck', control.value)
            .subscribe(
                success=>{
                    resolve(null);
                },
                error=>{
                    resolve({passwordValid: false})
                }
            )
    })
}

However now, the validation is triggered at each keystroke. I need to add debounce functionality. How can I do that?

like image 821
uksz Avatar asked Jul 11 '16 11:07

uksz


1 Answers

It's not possible out of the box since the validator is directly triggered when the input event is used to trigger updates. See this line in the source code:

  • https://github.com/angular/angular/blob/master/modules/angular2/src/common/forms/directives/default_value_accessor.ts#L23

If you want to leverage a debounce time at this level, you need to get an observable directly linked with the input event of the corresponding DOM element. This issue in Github could give you the context:

  • https://github.com/angular/angular/issues/4062

In your case, a workaround would be to implement a custom value accessor leveraging the fromEvent method of observable.

Here is a sample:

const DEBOUNCE_INPUT_VALUE_ACCESSOR = new Provider(
  NG_VALUE_ACCESSOR, {useExisting: forwardRef(() => DebounceInputControlValueAccessor), multi: true});

@Directive({
  selector: '[debounceTime]',
  //host: {'(change)': 'doOnChange($event.target)', '(blur)': 'onTouched()'},
  providers: [DEBOUNCE_INPUT_VALUE_ACCESSOR]
})
export class DebounceInputControlValueAccessor implements ControlValueAccessor {
  onChange = (_) => {};
  onTouched = () => {};
  @Input()
  debounceTime:number;

  constructor(private _elementRef: ElementRef, private _renderer:Renderer) {

  }

  ngAfterViewInit() {
    Observable.fromEvent(this._elementRef.nativeElement, 'keyup')
      .debounceTime(this.debounceTime)
      .subscribe((event) => {
        this.onChange(event.target.value);
      });
  }

  writeValue(value: any): void {
    var normalizedValue = isBlank(value) ? '' : value;
    this._renderer.setElementProperty(this._elementRef.nativeElement, 'value', normalizedValue);
  }

  registerOnChange(fn: () => any): void { this.onChange = fn; }
  registerOnTouched(fn: () => any): void { this.onTouched = fn; }
}

And use it this way:

function validator(ctrl) {
  console.log('validator called');
  console.log(ctrl);
}

@Component({
  selector: 'app'
  template: `
    <form>
      <div>
        <input [debounceTime]="2000" [ngFormControl]="ctrl"/>
      </div>
      value : {{ctrl.value}}
    </form>
  `,
  directives: [ DebounceInputControlValueAccessor ]
})
export class App {
  constructor(private fb:FormBuilder) {
    this.ctrl = new Control('', validator);
  }
}

See this plunkr: https://plnkr.co/edit/u23ZgaXjAvzFpeScZbpJ?p=preview.

like image 118
Thierry Templier Avatar answered Sep 28 '22 08:09

Thierry Templier