Constantin Beer
Constantin Beer

Reputation: 5835

How to trigger async validator after untouching input field in Angular with reactive forms?

I'm trying to achieve that my async validator only gets triggered after I untouched an input field.

Right now I trigger the async validator with every letter I write into the input field. Since the async validator triggers a Firebase Cloud Function which even triggers a read in a Firebase Firestore it costs too much for only just checking if a specific username exists.

So is there a way to trigger the async validator only after an user touches another input field or a button? Or even something completely different which could solve this problem?

Following is my code:

SignUpComponent TS

export class SignUpComponent implements OnInit {
  signUpForm: FormGroup;

  constructor(
    private authService: AuthService,
    private userService: UserService,
    private formBuilder: FormBuilder,
    private router: Router) {
  }

  ngOnInit() {
    this.signUpForm = this.formBuilder.group({
      username: [null, Validators.required, this.existingUsername.bind(this)],
      ...
    });
  }

  signUp() {
    ...
  }

  // performance is bad - one doc read and one function call for each letter entered
  existingUsername(control: FormControl): Promise<any> | Observable<any> {
    return this.userService.checkUsername(control.value)
      .pipe(
        map(res => {
          return res.usernameAvailable ? null : {existingUsername: true};
        })
      );
  }
}

SignUpComponent HTML

<form [formGroup]="signUpForm" (ngSubmit)="signUp()">
  <mat-form-field>
    <input matInput placeholder="Username" formControlName="username">
    <mat-error *ngIf="signUpForm.get('username').hasError('required')">
      Please insert a username!
    </mat-error>
    <mat-error *ngIf="signUpForm.get('username').hasError('existingUsername')">
      Username is already in use!
    </mat-error>
    <mat-hint *ngIf="signUpForm.status === 'PENDING'">
      Checking...
    </mat-hint>
  </mat-form-field>
</form>

Upvotes: 1

Views: 5632

Answers (2)

bryan60
bryan60

Reputation: 29325

use the updateOn option in your form control, something like:

username: [null, { 
  validators: Validators.required, 
  asyncValidators: this.existingUsername.bind(this), 
  updateOn: 'blur'}],

this tells angular to only run validation on blur rather than value change.

However, I do find this solution to be sub optimal in this use case, as users expect things to validate as they type. I find a better method is taking advantage of the way angular automatically cancels prior requests and switches on each new value by using ‘timer’ to debounce your validation, in your validator function, you’d just wrap your observable in something like:

 return timer(300).pipe(switchMap(() => /* actual validator */ ))

This will make sure the validation request only runs if the user has not changed the value for 300ms, as the previous validation will be canceled if the user changes the value.

Upvotes: 3

spots
spots

Reputation: 2708

I think you want to update the form on blur

const control = new FormControl('', { updateOn: 'blur' });

Upvotes: 2

Related Questions