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

Angular2 Dependency Injection with NPM-MQTT

I'm facing some challenges integrating the MQTT package into my Angular2 TypeScript project (created with angular-cli). When using var mqtt = require('mqtt');, I encounter the error Cannot find name 'require'. Therefore, I attemp ...

Having issues with Craco not recognizing alias configuration for TypeScript in Azure Pipeline webpack

I am encountering an issue with my ReactJs app that uses Craco, Webpack, and Typescript. While the application can run and build successfully locally, I am facing problems when trying to build it on Azure DevOps, specifically in creating aliases. azure ...

Utilizing lazy loading in Angular with 2 modules that require sharing the same service

Custom Module Integration: export class CustomModule { static forRoot(): ModuleWithProviders { return { ngModule: CustomModule, providers: [ MyService ] }; } } Dynamic Module #1: @NgModule({ imports: [ CommonM ...

Having trouble triggering an alert() after a successful post to the database

As I delve into the realm of backend development, I find myself facing what seems like a simple puzzle that I just can't solve. My goal is to create a basic CRUD app to enhance my skills, so I decided to build a blog platform that could eventually be ...

When I utilize the redux connect function, the class information in my IDE (PhpStorm/WebStorm) seems to disappear

When I use the connect function from redux, it seems to hinder my IDE (PhpStorm) from "Find Usages" on my classes. This is likely because connect returns any, causing the type information from the imported SomeClass file to be lost. export default connect ...

Removing an image from the files array in Angular 4: A step-by-step guide

I have recently started working with typescript and I am facing a challenge. I need to remove a specific image from the selected image files array before sending it to an API. app.component.html <div class="row"> <div class="col-sm-4" *ngFor ...

Encountering difficulty when integrating external JavaScript libraries into Angular 5

Currently, I am integrating the community js library version of jsplumb with my Angular 5 application (Angular CLI: 1.6.1). Upon my initial build without any modifications to tsconfig.json, I encountered the following error: ERROR in src/app/jsplumb/jspl ...

Angular2 allows you to create pipes that can filter multiple values from JSON data

My program deals with an array of nested json objects that are structured as follows: [{name: {en:'apple',it:'mela'}},{name:{en:'coffee',it:'caffè'}}] I am looking to implement a pipe that can filter out objects b ...

Leveraging and utilizing TypeScript npm packages

My goal is to create shared code in TypeScript, package it as an npm package, and easily install it. I attempted to create an external library like this: export class Lib { constructor(){ } getData(){ console.log('getting data from l ...

Webpack is failing to recognize certain CSS files

My development stack includes Vue.js 2.5.15, Webpack 4.12.0, css-loader 0.28.11, ASP.Net Core 2.1 in Visual Studio 2017. Starting with the Visual Studio asp.net core template project for Vue and Typescript, I prefer to have individual small CSS files with ...

Has the object been altered to become undefined?

product.service.ts: import { Injectable } from '@angular/core'; import { Http, Response } from '@angular/http'; import { Observable } from "rxjs/Observable"; import 'rxjs/add/operator/map' @Injectable() export class ProductSe ...

Modify the standard localStorage format

I'm encountering a dilemma with my two applications, located at mysite.com/app1 and mysite.com/app2. Both of these apps utilize similar localStorage keys, which are stored directly under the domain "mysite.com" in browsers. This setup results in the l ...

Troubles arise when compiling TypeScript to JavaScript

I have been experimenting with TypeScript, specifically for working with classes. However, I am facing an issue after compiling my TS file into JS. Below is the TypeScript code for my class (PartenaireTSModel.ts): export namespace Partenaires { export ...

Determine the outcome of the subscription using an if/else statement

I'm working on a function that needs to return a boolean value based on the result of an http post request. Here's what I have so far: checkPost(callRequest: boolean): boolean { console.log('START REQUEST'); if(call ...

Encountered an HttpErrorResponse while trying to access the API endpoint

I am encountering an issue when trying to update and insert data with a single post request. Below is my API code: Here is the service code: This section includes the function calling code: Additionally, this is the model being used The API C# model c ...

What is the reason TypeScript does not recognize the type when dealing with promises?

I am encountering an unexpected behavior where there is no error even though there should be one in TypeScript when using promises. I assigned a number value to a string variable, but surprisingly, no error was thrown. Why does this happen? https://codesa ...

Tips for extracting information from Observable that is supplied with data from an external API

Beginner in the world of Reactive programming (and Angular). I’m eager to create my first data stream and receive something other than an error message in the console output :) Here is what I've attempted: export class AppComponent { requestS ...

A guide on accessing a dynamic object key in array.map()

How can I dynamically return an object key in array.map()? Currently, I am retrieving the maximum value from an array using a specific object key with the following code: Math.max.apply(Math, class.map(function (o) { return o.Students; })); In this code ...

The 'propTypes' property is not found on the 'typeof TextInput' type declaration

Trying my hand at creating a react-native component using Typescript for the first time, but I ran into an issue on line 51: Property 'propTypes' does not exist on type 'typeof TextInput Couldn't find any helpful information on similar ...

Angular6 ng test is not successful

I recently upgraded my project from Angular4 to version 6. Everything seems to be working fine, except for the issue with running ng test: ERROR [karma-server]: Server start failed on port 9876: Error: No provider for "framework:@angular/cli"! (Resolving ...