Official Documentation View Source on Github

Highlights

  1. Use the [(ngModel)] attribute for two-way binding.
  2. Access each field validation errors through ngModel.
  3. Access the form validation state through ngForm.

// app/simple-form/user.ts

export interface User {
  id: number;
  name: string;
  birthdate: Date;
  favoriteColor?: string;
}
<!-- app/simple-form/user-form-template/user-form-template.component.html -->

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

<mat-card>
  <form (ngSubmit)="submit()" #userForm="ngForm">
    <mat-form-field>
      <mat-label>Name</mat-label>
      <input
        matInput
        required
        name="name"
        [(ngModel)]="user.name"
        #name="ngModel"
      />
      <mat-error *ngIf="name.errors?.required"
        >You must enter a value</mat-error
      >
    </mat-form-field>
    <mat-form-field>
      <mat-label>Birth date</mat-label>
      <input
        matInput
        required
        name="birthdate"
        [matDatepicker]="picker"
        [(ngModel)]="user.birthdate"
        #birthdate="ngModel"
      />
      <mat-datepicker-toggle matSuffix [for]="picker"></mat-datepicker-toggle>
      <mat-datepicker #picker></mat-datepicker>
      <mat-error *ngIf="birthdate.errors?.required"
        >You must enter a value</mat-error
      >
    </mat-form-field>
    <mat-form-field>
      <mat-label>Favorite color</mat-label>
      <mat-select name="favoriteColor" [(ngModel)]="user.favoriteColor">
        <mat-option *ngFor="let color of colors" [value]="color">{{
          color
        }}</mat-option>
      </mat-select>
    </mat-form-field>
    <div class="form-buttons">
      <button
        mat-raised-button
        type="submit"
        color="primary"
        [disabled]="loading || !userForm.form.valid"
      >
        Submit
      </button>
    </div>
  </form>
</mat-card>

<h2>User (JSON)</h2>

<pre
  >{{ user | json }}
</pre>
// app/simple-form/user-form-template/user-form-template.component.ts

import { Component } from '@angular/core';
import { MatSnackBar } from '@angular/material/snack-bar';
import { finalize } from 'rxjs/operators';
import { UserService } from 'src/app/simple-form/user.service';
import { User } from '../user';

@Component({
  selector: 'app-user-form-template',
  templateUrl: './user-form-template.component.html',
})
export class UserFormTemplateComponent {
  readonly colors = ['Red', 'Green', 'Blue'];
  readonly user: User = {
    id: 0,
    name: '',
    birthdate: new Date(),
    favoriteColor: '',
  };
  loading = false;

  constructor(
    private readonly userService: UserService,
    private readonly snackBar: MatSnackBar
  ) {}

  submit(): void {
    this.loading = true;
    this.userService
      .save(this.user)
      .pipe(finalize(() => (this.loading = false)))
      .subscribe(({ id }) => {
        this.snackBar.open(`User saved with ID #${id}`, 'Close');
      });
  }
}
// app/simple-form/user.service.ts

import { Injectable } from '@angular/core';
import { Observable, of } from 'rxjs';
import { delay } from 'rxjs/operators';
import { User } from 'src/app/simple-form/user';

export interface UserSaveResponse {
  id: number;
}

@Injectable({
  providedIn: 'root',
})
export class UserService {
  save(user: User): Observable<UserSaveResponse> {
    console.log('Save:', user);
    return of({
      id: 1,
    }).pipe(delay(1000));
  }
}