Material Form Components — mat-form-field, mat-select and mat-datepicker

Angular Material’s form components provide polished, accessible form UI that integrates with both template-driven and reactive forms. mat-form-field is the wrapper that provides consistent labels, hints, error messages, and prefix/suffix support for any form input. Connecting Material form components to reactive forms requires understanding which directive to use on which element — matInput for text inputs, mat-select for dropdowns, matDatepicker for date pickers.

Material Form Field Components

import {
  MatFormFieldModule, MatInputModule, MatSelectModule,
  MatAutocompleteModule, MatDatepickerModule,
  MatNativeDateModule, MatChipsModule, MatIconModule,
} from '@angular/material';

@Component({
  standalone:  true,
  imports:    [ReactiveFormsModule, MatFormFieldModule, MatInputModule,
               MatSelectModule, MatAutocompleteModule, MatDatepickerModule,
               MatNativeDateModule, MatChipsModule, MatIconModule, MatButtonModule],
  template: `
    <form [formGroup]="filterForm" class="filter-form">

      <!-- Text input with icon prefix and character count suffix ─────── -->
      <mat-form-field appearance="outline">
        <mat-label>Search Posts</mat-label>
        <mat-icon matPrefix>search</mat-icon>
        <input matInput formControlName="search" placeholder="Title or keyword">
        <mat-hint align="end">
          {{ filterForm.controls.search.value?.length ?? 0 }}/100
        </mat-hint>
        <button mat-icon-button matSuffix type="button"
                *ngIf="filterForm.controls.search.value"
                (click)="filterForm.controls.search.reset()">
          <mat-icon>close</mat-icon>
        </button>
      </mat-form-field>

      <!-- Select dropdown ──────────────────────────────────────────── -->
      <mat-form-field appearance="outline">
        <mat-label>Status</mat-label>
        <mat-select formControlName="status">
          <mat-option value="">All Statuses</mat-option>
          <mat-option value="draft">Draft</mat-option>
          <mat-option value="review">In Review</mat-option>
          <mat-option value="published">Published</mat-option>
        </mat-select>
      </mat-form-field>

      <!-- Multi-select for categories ─────────────────────────────── -->
      <mat-form-field appearance="outline">
        <mat-label>Categories</mat-label>
        <mat-select formControlName="categories" multiple>
          @for (cat of categories(); track cat.id) {
            <mat-option [value]="cat.id">{{ cat.name }}</mat-option>
          }
        </mat-select>
      </mat-form-field>

      <!-- Autocomplete ──────────────────────────────────────────── -->
      <mat-form-field appearance="outline">
        <mat-label>Author</mat-label>
        <input matInput formControlName="author"
               [matAutocomplete]="authorAuto">
        <mat-autocomplete #authorAuto="matAutocomplete"
                          [displayWith]="displayAuthor">
          @for (author of filteredAuthors(); track author.id) {
            <mat-option [value]="author">{{ author.displayName }}</mat-option>
          }
        </mat-autocomplete>
      </mat-form-field>

      <!-- Date range picker ────────────────────────────────────────── -->
      <mat-form-field appearance="outline">
        <mat-label>Date Range</mat-label>
        <mat-date-range-input [rangePicker]="picker">
          <input matStartDate formControlName="dateFrom" placeholder="Start date">
          <input matEndDate   formControlName="dateTo"   placeholder="End date">
        </mat-date-range-input>
        <mat-datepicker-toggle matIconSuffix [for]="picker" />
        <mat-date-range-picker #picker />
      </mat-form-field>

    </form>
  `,
})
export class PostFilterFormComponent {
  private fb      = inject(FormBuilder);
  categories      = signal<CategoryDto[]>([]);
  filteredAuthors = signal<AuthorDto[]>([]);

  filterForm = this.fb.nonNullable.group({
    search:     [''],
    status:     [''],
    categories: [[] as number[]],
    author:     [null as AuthorDto | null],
    dateFrom:   [null as Date | null],
    dateTo:     [null as Date | null],
  });

  displayAuthor(author: AuthorDto | null): string {
    return author?.displayName ?? '';
  }
}
Note: mat-autocomplete requires the [matAutocomplete] directive on the input and the #auto="matAutocomplete" reference variable on the mat-autocomplete element. The displayed value in the input after selection is controlled by [displayWith]="displayFn" — a function that takes the selected option’s value and returns the string to display. Without displayWith, the input shows the raw object [object Object] after selection.
Tip: Use mat-hint and mat-error inside mat-form-field for contextual help and validation messages. Hints appear below the field by default (or at align="end" for right-aligned). Errors replace hints when the control is in error state. You can have multiple mat-error elements — Material shows all that are visible (their visibility is controlled by your *ngIf conditions inside each mat-error).
Warning: The MatNativeDateModule uses the browser’s native Date object which has locale and formatting limitations. For production applications with date formatting requirements, use MatMomentDateModule (requires moment.js) or MatLuxonDateModule (requires luxon) for full locale-aware date formatting. Install via npm install @angular/material-moment-adapter moment and provide MatMomentDateModule instead of MatNativeDateModule.

Common Mistakes

Mistake 1 — Using input without matInput inside mat-form-field (unstyled input)

❌ Wrong — <mat-form-field><input formControlName="x"></mat-form-field> without matInput; Material styling not applied.

✅ Correct — always add matInput directive: <input matInput formControlName="x">.

Mistake 2 — Missing displayWith on mat-autocomplete (shows [object Object])

❌ Wrong — autocomplete selects an object but no displayWith provided; input shows “[object Object]”.

✅ Correct — provide [displayWith]="displayFn" that extracts the display string from the option value.

🧠 Test Yourself

A mat-form-field has both mat-hint and mat-error elements. The form control is invalid and touched. What appears below the field?