Can Angular reactive forms be used to validate based on external conditions?

Currently, I am exploring Angular reactive forms validation and facing an issue with implementing Google autocomplete in an input field:

<input autocorrect="off" autocapitalize="off" spellcheck="off" type="text" class="input-auto input" formControlName="address">

The autocomplete feature suggests places as you type a keyword:

https://i.stack.imgur.com/ASr7n.png

I want to validate the address by ensuring it includes a zip code. Only addresses with a zip code should be considered valid. When a suggestion is chosen, the validation should occur based on whether the selected address contains a zip code.

The address information from Google Places API populates global variables (zipCode, countryName, cityName, streetName) in one form control for the entire address:

    this.mapsAPILoader.load().then(() => {
      const autocomplete = new window['google'].maps.places.Autocomplete(this.searchElementToRef.nativeElement, {
        types: ['address']
      });
      autocomplete.addListener('place_changed', () => {
        this.ngZone.run(() => {

          const place = autocomplete.getPlace();

          if (place.geometry === undefined || place.geometry === null) {
            return;
          }

          this.form.get('address').setValue(place.formatted_address);

          for (const addressComponent of place.address_components) {
            for (const addressComponentType of addressComponent.types) {
              switch (addressComponentType) {
                case 'postal_code':
                  this.zipCode = addressComponent.long_name;
                  break;
                case 'country':
                  this.countryName = addressComponent.long_name;
                  break;
                case 'locality':
                  this.cityName = addressComponent.long_name;
                  break;
                case 'route':
                  this.streetName = addressComponent.long_name;
                  break;
              }
            }
          }
        });
      });
    });

In the method that creates the form using FormBuilder, a custom validator is used:

  public createFormGroup(): FormGroup {
    return this.fb.group({
      address: [null, this.zipCodeValidator()]
    });
  }

The zipCodeValidator() custom validation method is expected to trigger an error when the address lacks a zipCode:

  public zipCodeValidator(): ValidatorFn {
    return (control: AbstractControl): Observable<{ [key: string]: boolean } | null> => {
      if (this.zipCode !== undefined) {
        return of({ zipCode: true });
      }
      return null;
    };
  }

However, regardless of whether a zip code is present, the validation always passes as valid:

https://i.stack.imgur.com/7evlc.png

When using the form control's value as a condition, the validation works correctly. Modifying the validation method as shown below triggers an error when no input is provided:

  public zipCodeValidator(): ValidatorFn {
    return (control: AbstractControl): Observable<{ [key: string]: boolean } | null> => {
      if (control.value === null) {
        return of({ noValue: true });
      }
      return null;
    };
  }

Question:
Is it feasible to validate the form under these conditions, considering that the zipCode value is not directly passed to any form control?

Answer №1

The solution lies in the ability to validate based on conditions outside of the form, which proved to be quite beneficial for me.

Initially, I encountered some issues with the validation method as I mistakenly used the Observable for async-validation. However, upon rectifying this error, the method only returned the object without the Observable:

  public zipCodeValidator(): ValidatorFn {
    return (control: AbstractControl): { [key: string]: boolean } | null => {
      if (control.value !== null) {
        // additional conditions can be added when a value exists
        if (this.zipCode !== undefined) {
          return { zipCode: true };
        }
      } else {
          // handle cases where no value has been entered, similar to validator.required
          return { empty: true}
        }
      return null;
    };
  }

Upon further examination, I discovered that the validation was being conducted based on the previous state of the zipCode variable. Realizing my mistake, I found a simpler solution by adjusting the timing of setting the address form control value:

this.form.get('address').setValue(place.formatted_address);

Once I moved this line to the correct location after extracting the zip code data, everything fell into place:

this.mapsAPILoader.load().then(() => {
  const autocomplete = new window['google'].maps.places.Autocomplete(this.searchElementToRef.nativeElement, {
    types: ['address']
  });
  autocomplete.addListener('place_changed', () => {
    this.ngZone.run(() => {

      const place = autocomplete.getPlace();

      if (place.geometry === undefined || place.geometry === null) {
        return;
      }

      for (const addressComponent of place.address_components) {
        for (const addressComponentType of addressComponent.types) {
          switch (addressComponentType) {
            case 'postal_code':
              this.zipCode = addressComponent.long_name;
              break;
            case 'country':
              this.countryName = addressComponent.long_name;
              break;
            case 'locality':
              this.cityName = addressComponent.long_name;
              break;
            case 'route':
              this.streetName = addressComponent.long_name;
              break;
          }
        }
      }

      this.form.get('address').setValue(place.formatted_address);
    });
  });
});

If the need arises to trigger the validation at a different point in time rather than from the beginning, it's essential to update the validators accordingly:

this.form.get('address').setValidators(this.zipCodeValidator());
this.form.get('address').updateValueAndValidity();

Furthermore, removing the validators at any given moment is a simple process:

this.form.get('address').clearValidators();

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

What is the best way to transfer data from .ts and .html files to css/scss in an Angular

I'm currently working on a MEAN stack application and I would like to allow users to customize the theme color. The selected color should be saved in MongoDB so that every time the user opens the application, it remains consistent. I would appreciate ...

NPM: There are no valid TypeScript file rules specified

Currently working on a small project using React.JS. Whenever I execute : npm run start, the following message gets logged: Starting type checking and linting service... Using 1 worker with 2048MB memory limit Watching: /Users/John/Projects/myProject/src ...

Optimal strategies for managing server-side validation/errors in Angular applications

Back in the day, I used to retrieve HTTP responses with a TypeScript object. validateTokenHttp(token: string): Observable<User> { return this.http.get<User>(`${environment.api}/auth/verifyToken/${token}`); } Sometimes it would return a Us ...

Transform the header elements into clickable links

How can I prevent the header items from acting as links when I right-click on them? Here is my code: (I don't want them to open in another window when right-clicked.) [![enter image description here][1]][1] ...

Getting meta values in Angular2 is a common task that can be achieved through

Is there a way to retrieve the meta value from the HTML head in Angular2? For instance, let's say I have the following HTML structure of my project and I want to extract the meta tag with the name "userId". <!doctype html> <html> <he ...

Obtaining images from the backend within a component's TypeScript file in a MEAN stack application is a

In my component.ts file, I am looking to retrieve images from the backend/images folder as Image objects. The paths of these images are stored in the database. this.productsService.getProduct(this.editId).subscribe(productData => { this.name = prod ...

Set the subscription's value to the class property without changing its original state

Lately, I have been using the following method to set the value of a subscription to a property in my classes: export class ExampleComponent implements OnInit { exampleId: string; constructor(public route: ActivatedRoute) { this.route.params.subs ...

Is there a way to streamline this generator without using recursion?

I need to develop a unique value generator that produces values within a specified range. The criteria are: all generated values must be distinct the order of values remains consistent upon each run of the generator each value should be significantly diff ...

Angular - ALERT TypeMismatchError: Unable to access properties of null (attempting to access 'car_photo')

One issue I encountered while working on my Angular-12 project involves editing data that includes an image. Below is the relevant code snippets: interface: export class VehicleResponse { results!: { vehicle: IVehicle }; } export interface IVehicle { ...

The method insertFusionCharts cannot be called in Angular when using jQuery

I have integrated the following scripts into my angular project <script defer src="https://cdnjs.cloudflare.com/ajax/libs/materialize/1.0.0/js/materialize.min.js"></script> <script src="assets/js/jquery.min.js"></script> <script ...

Issue with PassportJS and Express 4 failing to properly store cookies/session data

I have a situation with my Express 4 app using Passport 0.3.2. I've set up a passport-local strategy, and it's successfully retrieving the user information when the /session endpoint is provided with a username and password. The issue arises whe ...

TypeScript: The capability to deduce or derive values for a type from a constant object literal that is nested with non-distinct keys

I'm trying to figure out a way to utilize TS/IDE to display specific literal values from a style guide object, instead of just the inferred type. Here's an example: const guide = { colors: { black: { medium: "#000000", } ...

Encountering Issues with NextJS Dynamic SSR: Mobile Devices stuck on loading screen

Issue: The dynamic import feature of Next JS is encountering loading issues specifically on mobile browsers such as Google Chrome and Safari on IOS. Strangely, the functionality works smoothly on desktop browsers like Google Chrome and Mozilla. The projec ...

Having trouble getting useFieldArray to work with Material UI Select component

I am currently working on implementing a dynamic Select field using Material UI and react-hook-form. While the useFieldArray works perfectly with TextField, I am facing issues when trying to use it with Select. What is not functioning properly: The defau ...

A guide on transforming a string into an array of objects using Node.js

Hey everyone, I have a single string that I need to convert into an array of objects in Node.js. let result = ""[{'path': '/home/media/fileyear.jpg', 'vectors': [0.1234, 0.457, 0.234]}, {'path': '/home/med ...

Outputting data stored in Firestore object

Is there a way to display the content of a Firestore object in Angular Firebase? I am working on a project where I need to retrieve and print the name of a document stored in Firestore. In my Firestore database, I have a parent collection called "nforms" w ...

Choose does not showcase the updated value

My form contains a form control for currency selection Each currency object has the properties {id: string; symbol: string}; Upon initialization, the currency select component loops through an array of currencies; After meeting a specific condition, I need ...

Assign object properties to a constant variable while validating the values

When receiving the features object, I am assigning its values to constants based on their properties. const { featureCode, featureSubType, contentId, price, family: { relationCountsConfig: { motherCount, fatherCount, childrenCount }, max ...

Setting up Jest to run in WebStorm for a Vue project with TypeScript can be done through

I am struggling to run all my tests within WebStorm. I set up a project with Babel, TypeScript, and Vue using vue-cli 3.0.0-rc3. My run configuration looks like this: https://i.stack.imgur.com/7H0x3.png Unfortunately, I encountered the following error: ...

Enter your phone number in the designated field

I am looking to receive mobile numbers from various countries as input. I attempted to achieve this using the following code: <input type="tel" minlength="8" maxlength="10" name="Number" id="Number" pattern="[0-9]{3}-[0-9]{2}-[0-9]{3}" class="form-co ...