My requirement is to perform reactive form field validations in such a way that the error messages are displayed only after the user stops typing.
How can I accomplish this using reactive forms and Rxjs debounceTime?
I'm looking for a solution that works with Reactive forms
The Debouncetime operator starts counting time after it receives a value. If the source observable emits a value before the timeout duration, then counting is reset to zero & started again. When the timeout duration elapses the operator emits the last value and the counting stops.
Angular 6 onwards, debounceTime is imported as following. import { debounceTime } from 'rxjs/operators'; It is used with pipe operator of Observable . debounceTime is useful in operation where user changes inputs frequently such as search operation.
debounceTime delays the values emitted by a source for the given due time. If within this time a new value arrives, the previous pending value is dropped and the timer is reset. In this way debounceTime keeps track of most recent value and emits that most recent value when the given due time is passed.
Debouncing is the delay of a function/method execution or an action for a period of the specified time. During this specified time, calls to the method/function or action are collected and executes each one when the specified has elapsed.
The (or at least a) way to get this to work is to dynamically remove and add your validators as you go.
On your input(s), use a keydown
binding that will strip away validators when the user starts to type, and a keyup
binding that will run through a debounceTime pipe and then reapply the validators (but only after the specified debounce time has passed).
Code here:
import { Component, OnInit } from '@angular/core';
import { FormBuilder, FormGroup, Validators } from '@angular/forms';
import { Subject } from 'rxjs';
import { debounceTime } from 'rxjs/operators';
@Component({
selector: 'form-component',
template: `
<form [formGroup]="formGroup">
<input type="text" formControlName="name" (keyup)="onKeyUp()" (keydown)="onKeyDown()" [ngClass]="{ 'invalid': formGroup.controls.name.invalid }">
</form>
`,
styles: [
'.invalid { border-color: red; color: red; }'
]
})
export class FormComponent implements OnInit {
formGroup: FormGroup;
subject: Subject<any> = new Subject();
constructor(private formBuilder: FormBuilder) {}
ngOnInit(): void {
this.formGroup = this.formBuilder.group({
name: [ '' ]
});
// Subscribe to the subject, which is triggered with each keyup
// When the debounce time has passed, we add a validator and update the form control to check validity
this.subject
.pipe(debounceTime(500))
.subscribe(() => {
this.formGroup.controls.name.setValidators([ Validators.minLength(5) ]);
this.formGroup.controls.name.updateValueAndValidity();
}
);
}
onKeyUp(): void {
this.subject.next();
}
onKeyDown(): void {
// When the user starts to type, remove the validator
this.formGroup.controls.name.clearValidators();
}
}
And StackBlitz here: https://stackblitz.com/edit/debounce-validator
debounceTime
waits for the time period mentioned and then calls the subscribe method. e.g; debounceTime(1000)
will wait for 1 second. It is implemented through pipes
.
this can be added to any subscribe method. Following is the working example
import { Component, OnInit } from '@angular/core';
import { Validators, AbstractControl } from '@angular/forms';
import { debounceTime } from 'rxjs/operators';
// dynamic forms
import { FormGroup, FormControl, FormBuilder } from '@angular/forms';
@Component({
selector: 'app-customer-form',
templateUrl: './customer-form.component.html',
})
export class CustomerFormComponent implements OnInit {
emailMessage : string;
private validationMessages = {
required: "Email field is required",
email: "Please enter a valid Email"
}
customerForm: FormGroup;
customer = new Customer();
constructor(private fb: FormBuilder) { }
ngOnInit() {
this.customerForm = this.fb.group({
emailAddress: ['',
[
Validators.required,
Validators.email
]
]
})
const emailControl = this.customerForm.get('emailAddress');
emailControl.valueChanges.pipe( debounceTime(1000) ).subscribe(
value => this.setEmailMessage(emailControl)
)
}
setEmailMessage( c: AbstractControl ) : void {
this.emailMessage = '';
if ( (c.touched || c.dirty) && c.errors ) {
this.emailMessage = Object.keys(c.errors).map( key => this.validationMessages[key]).join(' ');
}
}
}
in your template
<input
class="form-control"
id="emailId" type="email"
placeholder="Email (required)"
formControlName="emailAddress"
[ngClass]="{ 'is-invalid': emailMessage }"/>
<span class="invalid-feedback">
{{ emailMessage }}
</span>
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