Q:

Is it possible in Angular reactive forms to validate on conditions out of the form?

I'm working on Angular reactive forms validation. I have an input with google autocomplete implemented on it:
<input autocorrect="off" autocapitalize="off" spellcheck="off" type="text" class="input-auto input" formControlName="address">

It is standard implementation, everytime you enter some keyword, you get places suggestions:

Now, what I would like to do is to validate the address. The address needs to contain zip code - only then should be valid. So once you type something and choose one of the suggestions, the validation should be fired. When chosen suggestion contains zip code is valid, when not - is invalid.
As you can see there is only one form control for the whole address (and this should stay like that), which I fill with formatted address from google API. I'm getting also the information about the address components from google places API, which I store in global variables (zipCode, countryName, cityName, streetName):
    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 which creates the form with FormBuilder I use custom validator:
  public createFormGroup(): FormGroup {
    return this.fb.group({
      address: [null, this.zipCodeValidator()]
    });
  }

With below custom validation method I would like to get the error when the zipCode is missing in address:
  public zipCodeValidator(): ValidatorFn {
    return (control: AbstractControl): Observable<{ [key: string]: boolean } | null> => {
      if (this.zipCode !== undefined) {
        return of({ zipCode: true });
      }
      return null;
    };
  }

However the form it is not correctly validated, because no matter I get the address with or without zip code - it is always valid:

If I use as a condition form control related value, the validation it's working. So updating the validation method to the below state generates an error when there is nothing entered to the input:
  public zipCodeValidator(): ValidatorFn {
    return (control: AbstractControl): Observable<{ [key: string]: boolean } | null> => {
      if (control.value === null) {
        return of({ noValue: true });
      }
      return null;
    };
  }

Question:
Is it possible to validate the form like that - with the conditions actually out of the form (since I do not pass this zipCode value to any form control directly)?

A:

The answer is, it is possible to validate on conditions out of the form and as usually it was very close to me.
First I've messed up with the validation method. Observable I've used is for async-validation. So after fixing it, the method will return the object only, not the Observable:
  public zipCodeValidator(): ValidatorFn {
    return (control: AbstractControl): { [key: string]: boolean } | null => {
      if (control.value !== null) {
        // if value exists, we can add number of conditions
        if (this.zipCode !== undefined) {
          return { zipCode: true };
        }
      } else {
          // here we can react when there is no value entered - act as validator.required
          return { empty: true}
        }
      return null;
    };
  }

Then I've started to get the validation done, but for the previous state of the zipCode variable. I thought because maybe I need to use the async-validation, but it was even simpler. I've set the value of address form control too early:   this.form.get('address').setValue(place.formatted_address);
So I've moved it behind the part where I'm looking for the zip code and it worked:
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);
    });
  });
});

There is also question about what if I want to fire the validation in other moment, not from beginning. Then just need to set it in the right place and update the validators:
this.form.get('address').setValidators(this.zipCodeValidator());
this.form.get('address').updateValueAndValidity();

Also if there is a need to remove it at some point, here is the easy way:
this.form.get('address').clearValidators();

