Perform validation for a form field in Angular 2 asynchronously by making an HTTP request

I have a concept where the user can submit a form and if the email is already registered, an error triggered by the API should display. I am using reactive forms with FormBuilder and trying to implement the validator in the subscribe error handler.

Constructor :

this.email = fb.control('', [Validators.required, Validators.pattern(/^[^\s@]+@[^\s@]+\.[^\s@]{2,}$/), SignupComponent.alreadyExist()]);
this.username = fb.control('', [Validators.required, Validators.pattern(/^([A-ZÀ-ÿa-z0-9]{2,})+(?:[ _-][A-ZÀ-ÿa-z0-9]+)*$/)]);

this.userRegisterForm = fb.group({
    email: this.email,
    username: this.username
});

Custom alreadyExist() validator :

static alreadyExist(alreadyExist: boolean = false): any {

    return (control: FormControl): { [s: string]: boolean } => {

        if(alreadyExist) {
            return { emailAlreadyExist: true }
        }
    }
}

onSubmit() :

this.user.create(newUser)
    .subscribe(
        data => {
            this.router.navigate(['signin']);
        },
        error => {
            if(error.status === 401) {

                // ATTEMPTING TO USE THE VALIDATOR HERE BY SETTING : FALSE
                SignupComponent.alreadyExist(true);
            }

            this.loading = false;
        });

Although it seems like the validator is being called, the anonymous method inside it is not activated. Is this not a recommended approach? Any suggestions would be greatly appreciated. Thank you.

Answer №1

After some research, I managed to find a solution.

In my specific scenario, when dealing with the email FormControl, I realized that I don't actually require a custom validator (despite the fact that it can be implemented using setValidators()).

I decided to remove alreadyExist() and eliminate its declaration from the list of validators.

Instead, I opted to utilize the setErrors() method provided by the FormControl :

onSubmit() :

this.user.create(newUser)
    .subscribe(
        data => {
            this.router.navigate(['signin']);
        },
        error => {
            if(error.status === 401) {

                // TRIGGER THE VALIDATOR HERE TO SET : FALSE
                this.userRegisterForm.controls['email'].setErrors({
                  "emailAlreadyExist": true
                });
            }

            this.loading = false;
        });

This approach adds a new error to the email FormControl, allowing me to associate an error message to this particular issue.

Answer №2

Hello Kevin! :) Based on what I understand, your current process flow is as follows:

  1. Allow the user to submit the form (for creating a new account).
  2. Manually add an error to the form if there was a failure in creating the new account.

This setup is not efficient because validation is happening at two points: before and after form submission.

What you should do is utilize a custom validator like you did initially. However, since checking for existing usernames or emails might involve an HTTP request, you need to implement an asynchronous validator.

In declaring the form, asynchronous validators should come AFTER synchronous validators:

this.userForm = fb.group({
  // `userExists` is placed third as it's an async validator
  username: ['', Validators.required, userExists],
  password: []
});

Now let's take a look at the code for userExists. Since this is an async validator, it needs to return an observable or a promise:

// This is just a regular function
// but you can also incorporate this code within a class method.
function userExists(control: AbstractControl): Observable<{[key: string]: any}> {
  // The userName that needs testing.
  const userName = control.value;

  // Note: In a real application, replace this observable with an actual HTTP request
  const existingUsernames = ['kevin', 'vince', 'bernard'];
  return Observable.create(observer => {
    // If the username already exists, emit an error
    if (existingUsernames.indexOf(userName) !== -1) {
      observer.next({usernameTaken: true});
    }
    // Username is available, emit null
    else {
      observer.next(null);
    }
    // The observable MUST complete.
    observer.complete();
  });
}

You can experiment with the code on this Plunker: https://plnkr.co/edit/aPtrp9trtmwUJDJHor6G?p=preview — Try entering an existing username ('kevin', 'vince', or 'bernard') and you'll see an error message even BEFORE submitting the form.

If your previous code wasn't functioning properly, it could be due to several reasons:

  • You used a synchronous validator instead of an asynchronous one.
  • Your validator function is a factory function (= a function that returns another function). This is necessary only if you need to customize the validator with specific parameters. In your case, you should stick to a regular function (i.e., in alreadyExist(), keep only the code following the return statement).
  • The approach of setting errors post-form submission was not ideal either.

Similar questions

If you have not found the answer to your question or you are interested in this topic, then look at other similar questions below or use the search

Angular 5: Create a dropdown menu with image options

Currently, I am utilizing a select element within Angular 5 while incorporating bootstrap 3. This component seems to be from WebForms / Bootstrap. I am curious if it is feasible to add images to the various options, possibly through the use of CSS? ...

The compatibility between Bootstrap DropDown and collapse features and angular2-meteor seems to be problematic

After installing bootstrap in my angular2-meteor project with the following command: meteor npm install --save <a href="/cdn-cgi/l/email-protection" class="__cf_email__" data-cfemail="2f4d40405b5c5b5d4e5f6f1b011f011f024e435f474e011a">[email pro ...

A guide on how to initiate a click event in Angular 5 using JQuery

I am trying to trigger a click event for each element based on its id, but it doesn't seem to be working. Below is the code I am using: ngOnInit() { this.getProductsLists(); } getProductsLists() { this.supplierService.getProductLists() .sub ...

What causes the distinction between entities when accessing objects through TestingModule.get() and EntityManager in NestJS?

Issue: When using TestingModule.get() in NestJS, why do objects retrieved from getEntityManagerToken() and getRepositoryToken() refer to different entities? Explanation: The object obtained with getEntityManagerToken() represents an unmocked EntityManag ...

Tips for effectively utilizing the "getFreeDragPosition" function in Angular 8 CdkDrag

Angular's CdkDrag API includes a method definition. However, how can this method be called in code? I attempted to call it like the example below but encountered an error. What is the correct procedure for utilizing such methods? export class Draga ...

Challenges with Angular and Bootstrap validation

In the Angular 18 website I'm developing, I have a code snippet for state selection which includes setting the initial value of info.state to null. This results in the disabled "Select..." option appearing in the select control. However, when applying ...

Ways to programmatically include routes with components sourced from a dynamically loaded module

One of my Angular components is dynamic and module-based: The module file is named dynamic-component.module.ts @NgModule({ imports: [ DynamicComponentRoutingModule, FormsModule, CommonModule, FormsModule, ], declarations: [ ...

The XMLHttpRequest response states that the preflight request did not meet the access control check requirements

I am attempting to subscribe to a firebase cloud messaging topic by sending an http post request. var data = null; var xhr = new XMLHttpRequest(); xhr.withCredentials = true; xhr.addEventListener("readystatechange", function () { if (this.readyState ...

Enhance the validation of multiple fields in Angular through asynchronous AbstractControl in groupForm

Ensuring the validity of a component is crucial. A valid component is defined by its unique brand, designation, type, reference, supplierId, and familyId. The challenge arises when one or more of these attributes are not unique. For example, if I'm ed ...

Retrieve specific files from a Firestore collection based on a particular field

I am currently working on a web application using Angular 6 and angularfire2. I have successfully retrieved all documents in a collection, but now I need to filter those documents to only get the ones with the field role.moderator == true. private users ...

unable to initiate a new project in angular version 8

Upon attempting to add a new project in Node.js command prompt, I encountered an error message stating: "The system cannot find the path specified." This has left me perplexed about how to proceed with creating a new project. Your environment is configure ...

Utilize Material-UI in Reactjs to showcase tree data in a table format

I am currently tackling a small project which involves utilizing a tree structure Table, the image below provides a visual representation of it! click here for image description The table displayed in the picture is from my previous project where I made ...

What is the significance of `new?()` in TypeScript?

Here is a snippet of code I'm working with in the TypeScript playground: interface IFoo { new?(): string; } class Foo implements IFoo { new() { return 'sss'; } } I noticed that I have to include "?" in the interface met ...

The `Required<Partial<Inner>>` does not inherit from `Inner`

I stumbled upon a code snippet that looks like this: type Inner = { a: string } type Foo<I extends Inner> = { f: I } interface Bar<I extends Inner> { b: I } type O<I extends Partial<Inner>> = Foo<Required<I>> & B ...

The cross-origin resource sharing configuration has not been implemented

I have SenseNet 7.0.0 and ASP.NET 5.2.3 set up, with my Angular (Typescript) application running on localhost:4200 and the ASP.NET application running on localhost:55064. I followed this tutorial for installing Sensenet and used this tutorial for installin ...

Angular 2 Endgame: HostBinding feature no longer functional

Here is how I used host-binding on a button attribute named "disabled" in the parent component of ng2-RC4: @Component({ selector: "nav-next", template: ` <div class="nav-next-directive" (click)="onClick($event)"> <button color ...

An issue occurred while compiling the 'ToastContainer' template. Decorators do not support function calls, and the call to 'trigger' caused an error

When I run ng serve and ng build, there are no errors. However, when I run ng build --prod, I encounter this error. Can anyone help me fix it? ERROR in Error during template compile of 'ToastContainer' Function calls are not supported in decor ...

Encountering 404 errors when reloading routes on an Angular Azure static web app

After deploying my Angular app on Azure static web app, I encountered an error. Whenever I try to redirect to certain routes, it returns a 404 error. However, if I navigate from one route to another within the app, everything works fine. I have attempted t ...

Transform object into data transfer object

Looking for the most efficient method to convert a NestJS entity object to a DTO. Assuming the following setup: import { IsString, IsNumber, IsBoolean } from 'class-validator'; import { Exclude } from 'class-transformer'; export clas ...

Some users are noticing that the style of the Angular Web Application is consistently fading away

Our team is currently facing a style issue with our Angular application where some users, including myself, are experiencing a lack of styling when accessing the platform. This results in a poor user experience, especially when users are redirected from an ...