Creating a Typed Reactive Angular Contact Form | Task

Ole Ersoy
Jun - 10  -  3 min

Scenario

We need a typed and reactive Contact form that gives us autocompletion for the form fields in our IDE and also provides error linting if we try to access fields that are not in the form.

Approach

Project Setup

We will create a Stackblitz and add @angular/material to the dependencies for the form UX.

Also to index.html add the link for Material Icons.

<link
href="https://fonts.googleapis.com/icon?family=Material+Icons"
rel="stylesheet"
/>

To styles.scss add the indigo pink theme.

@import '~@angular/material/prebuilt-themes/indigo-pink.css';
Initialize app.module.ts .

Initialize app.module.ts.

import { NgModule } from '@angular/core';
import { BrowserModule } from '@angular/platform-browser';
import { FormsModule, ReactiveFormsModule } from '@angular/forms';
import { MatInputModule } from '@angular/material/input';
import { MatFormFieldModule } from '@angular/material/form-field';
import { MatIconModule } from '@angular/material/icon';
import { MatButtonModule } from '@angular/material/button';

import { AppComponent } from './app.component';
import { BrowserAnimationsModule } from '@angular/platform-browser/animations';

@NgModule({
  imports: [
    BrowserModule,
    BrowserAnimationsModule,
    FormsModule,
    ReactiveFormsModule,
    MatInputModule,
    MatFormFieldModule,
    MatIconModule,
    MatButtonModule,
  ],
  declarations: [AppComponent],
  bootstrap: [AppComponent],
})
export class AppModule {}

Form Interface

First in app.component.ts define the interface for the form controls.

//===========================================
// Contact Form Interface
//===========================================
export interface IContactForm {
   name: FormControl<string>;
   email: FormControl<string>;
   phone?: FormControl<string | null>;
}

In the youtube that goes along with this article we point out the additional benefits of this interface.

Next define the form itself.

public form = new FormGroup<IContactForm>({
    name: new FormControl<string>('', { nonNullable: true }),
    email: new FormControl<string>('', { nonNullable: true }),
    phone: new FormControl<string | null>(`111-111-1111`, {
      nonNullable: false,
    }),
});

Note that we are passing in the Type of the form in the form constructor IContactForm.

Then bind the form controls to the template.

<form (ngSubmit)="onSubmit()" [formGroup]="form">
  <mat-form-field class="example-form-field" appearance="fill">
    <mat-label>Name</mat-label>
    <input matInput type="text" formControlName="name" />
    <button
      *ngIf="form.value.name"
      matSuffix
      mat-icon-button
      aria-label="Clear"
      (click)="form.controls.name.setValue('')"
    >
      <mat-icon>close</mat-icon>
    </button>
  </mat-form-field>

  <mat-form-field class="example-form-field" appearance="fill">
    <mat-label>Email</mat-label>
    <input matInput type="text" formControlName="email" />
    <button
      *ngIf="form.value.email"
      matSuffix
      mat-icon-button
      aria-label="Clear"
      (click)="form.controls.email.setValue('')"
    >
      <mat-icon>close</mat-icon>
    </button>
  </mat-form-field>

  <mat-form-field class="example-form-field" appearance="fill">
    <mat-label>Phone</mat-label>
    <input matInput type="tel" formControlName="phone" />
    <button
      *ngIf="form.value.phone"
      matSuffix
      mat-icon-button
      aria-label="Clear"
      (click)="form.controls.phone.setValue('')"
    >
      <mat-icon>close</mat-icon>
    </button>
  </mat-form-field>
  <br />
  <button mat-button type="submit">Submit</button>
  <br />
  <button mat-button (click)="reset()" type="reset">Reset</button>
</form>

<button mat-button (click)="removePhone()">Remove Phone Field</button>

<p [innerHTML]="data"></p>

Demo