Official Documentation View Source on Github

Highlights

  1. Pass a boolean expression to the validator to make it apply or not.
  2. Fields hidden by *ngIf will automatically recompute the validity of the form when shown.
  3. Clear hidden fields before submit. You can also clear them when they're hidden, but if the user decides to show them again, they will loose lost anything they had input.

// app/conditional-validation/profile.ts

export interface Profile {
  id: number;
  name: string;
  wantsPet: boolean;
  address?: string;
  petName?: string;
}
<!-- app/conditional-validation/profile-form-template/profile-form-template.component.html -->

<h1>Profile Form (template-based)</h1>

<mat-card>
  <form (ngSubmit)="submit()" #profileForm="ngForm">
    <mat-form-field>
      <mat-label>Name</mat-label>
      <input
        matInput
        required
        name="name"
        [(ngModel)]="profile.name"
        #name="ngModel"
      />
      <mat-error *ngIf="name.errors?.required"
        >You must enter a value</mat-error
      >
    </mat-form-field>
    <div>
      <mat-slide-toggle name="wantsPet" [(ngModel)]="profile.wantsPet"
        >I would like to adopt a new pet</mat-slide-toggle
      >
    </div>
    <mat-form-field>
      <mat-label>Address</mat-label>
      <input
        matInput
        [required]="profile.wantsPet"
        name="address"
        [(ngModel)]="profile.address"
        #address="ngModel"
      />
      <mat-error *ngIf="address.errors?.required"
        >You must enter a value</mat-error
      >
    </mat-form-field>
    <div>
      <mat-slide-toggle name="hasPet" [(ngModel)]="hasPet"
        >I currently have a pet</mat-slide-toggle
      >
    </div>
    <mat-form-field *ngIf="hasPet">
      <mat-label>Pet name</mat-label>
      <input
        matInput
        required
        name="petName"
        [(ngModel)]="profile.petName"
        #petName="ngModel"
      />
      <mat-error *ngIf="petName.errors?.required"
        >You must enter a value</mat-error
      >
    </mat-form-field>
    <div class="form-buttons">
      <button
        mat-raised-button
        type="submit"
        color="primary"
        [disabled]="loading || !profileForm.form.valid"
      >
        Submit
      </button>
    </div>
  </form>
</mat-card>

<h2>Profile (JSON)</h2>

<pre
  >{{ profile | json }}
</pre>
// app/conditional-validation/profile-form-template/profile-form-template.component.ts

import { Component } from '@angular/core';
import { MatSnackBar } from '@angular/material/snack-bar';
import { finalize } from 'rxjs/operators';
import { Profile } from '../profile';
import { ProfileService } from '../profile.service';

@Component({
  selector: 'app-profile-form-template',
  templateUrl: './profile-form-template.component.html',
})
export class ProfileFormTemplateComponent {
  readonly profile: Profile = {
    id: 0,
    name: '',
    wantsPet: false,
  };
  hasPet = false;
  loading = false;

  constructor(
    private readonly profileService: ProfileService,
    private readonly snackBar: MatSnackBar
  ) {}

  submit(): void {
    if (!this.hasPet) {
      this.profile.petName = undefined;
    }

    this.loading = true;
    this.profileService
      .save(this.profile)
      .pipe(finalize(() => (this.loading = false)))
      .subscribe(({ id }) => {
        this.snackBar.open(`Profile saved with ID #${id}`, 'Close');
      });
  }
}
// app/conditional-validation/profile.service.ts

import { Injectable } from '@angular/core';
import { Observable, of } from 'rxjs';
import { delay } from 'rxjs/operators';
import { Profile } from './profile';

export interface ProfileSaveResponse {
  id: number;
}

@Injectable({
  providedIn: 'root',
})
export class ProfileService {
  save(profile: Profile): Observable<ProfileSaveResponse> {
    console.log('Save:', profile);
    return of({
      id: 1,
    }).pipe(delay(1000));
  }
}