How To Best Use The Angular Material Form Field?

Photo by Markus Winkler on Unsplash

Blog Post #010

Duncan Faulkner November 2020

What is a mat-form-field component?

The mat-form-field is part of the Angular Material library and is found in the @angular/material/form-field namespace.

{MatFormFieldModule} from '@angular/material/form-field';

So what is a mat-form-field component? By itself this component doesn’t do much, this component affects other components by applying common styles to components that are wrapped within a mat-form-field. Typically components like input, textarea, mat-select, and mat-chip-list are wrapped in a mat-form-field this is considered a best practice, for example:

<mat-form-field>
    <input matInput>
</mat-form-field>

Below is the results rendered in the browser.

figure 1: legacy input

The input now has an underline underneath it. Selecting the input moves the text from the placeholder to a floating label.

figure 2: input selected placeholder now becomes floating label

This is the default appearance of the <mat-form-field> and is called Legacy, the others are Standard, Fill and Outline.

<mat-form-field appearance="legacy">
    <input placeholder="application label" matInput>
</mat-form-field>
<mat-form-field appearance="standard">
    <mat-label>application label</mat-label>
    <input matInput>
</mat-form-field>
<mat-form-field appearance="fill">
    <mat-label>application label</mat-label>
    <input matInput>
</mat-form-field>
<mat-form-field appearance="outline">
    <mat-label>application label</mat-label>
    <input matInput>
</mat-form-field>
figure 3: image of all four form-field appearances.

Standard: is an updated version of Legacy to bring it inline with Fill and Outline, the changes are minor and mainly around the spacing.

Fill: adds a background to the input and the placeholder is middle aligned (more space between the placeholder and the bottom line), the floating label remains with in the background.

Outline: adds an outline all round the outer edge of the input and the placeholder is middle aligned (more space between the placeholder and the bottom line), and the floating label is now on top of the outline.

Placeholders: In the Legacy appearance, the placeholder is promoted to a floating label. In Standard, Fill and Outline however, it is just a regular label. If you want a floating label in Standard, Fill and Outline you need to include a <mat-label>.

matInput: this is an Angular Material directive that allows native HTML input components (input, textarea, select for example) to interact with Angular Material.

Hint messages: The mat-form-field has two ways to assign a hint message. A hint message is a message that appears underneath the underline. If the hintLabel attribute of the mat-form-field is used then the message is left aligned (for the hintLabel attribute left aligned is the only option).

If the mat-hint tag is used then these can be left or right aligned, by setting the align attribute to either start or end.

figure 4: hintLabel – left aligned
<mat-form-field hintLabel="hint message" appearance="standard">
    <mat-label>Standard Input</mat-label>
    <input matInput/>
</mat-form-field>
figure 5: mat-hint – right aligned
<mat-form-field appearance="standard">
    <mat-label>Standard Input</mat-label>
    <input matInput/>
    <mat-hint align="end">hint message</mat-hint>
</mat-form-field>

Hint message appear underneath the underline. The mat-form-field can have hint messages set either from the mat-form-field or the mat-hint. To set the hint message using the mat-form-field set the hintLabel attribute to a message. With this option the message is left aligned.

<mat-form-field hintLabel="hint message" appearance="standard">
    <mat-label>Input</mat-label>
    <input matInput>
</mat-form-field>

Using the mat-hint element set a message and alignment, for example:

<mat-form-field appearance="standard">
    <mat-label>Input</mat-label>
    <input matInput>
    <mat-hint align="start">Message</mat-hint>
</mat-form-field>
<mat-form-field appearance="standard">
    <mat-label>Input</mat-label>
    <input matInput>
    <mat-hint align="end">Message</mat-hint>
</mat-form-field>

Adding error messages to a mat-form-field appear underneath the underline. To add an error message add the mat-error element and check the validity of the form control.

<mat-form-field appearance="standard">
    <mat-label>Input</mat-label>
    <input matInput [formControl]="control" required>
    <mat-error *ngIf="control.invalid">This is a required field.</mat-error>
  </mat-form-field>

The formControl is part of Angular’s forms module and is used to track the value and validity state of a control. Then in the mat-error we add an *ngIf to check to see if the formControl is invalid, if it we display the error message.

Hope you enjoyed this little tutorial, I will be writing up more articles on Angular Material, feel free to reach out to me on Twitter.

How To Best Update Angular To Include Angular Material?

Blog Post #009

Duncan Faulkner – August 2020

Adding Angular Material to a new or existing Angular project is a simple process. In the terminal, change the directory to your project and type:

ng add @angular/material

The Angular CLI will now install Angular Material, Angular CDK and Angular Animations and add these as a dependency to the project.

In previous versions of Angular (before v9) hammerjs was required for touch and gesture support.

npm install hammerjs

In the main.ts file in the imports at the top, include the following:

import 'hammerjs' // add this import
import { enableProdMode } from '@angular/core';
import { platformBrowserDynamic } from '@angular/platform-browser-dynamic';
import { AppModule } from './app/app.module';
import { environment } from './environments/environment';
if (environment.production) {
  enableProdMode();
}
platformBrowserDynamic().bootstrapModule(AppModule)
  .catch(err => console.log(err));

Starting with version 9 of Angular hammerjs is now optional, you no longer need to install this as a dependency, it is now part of @angular/platform-browser. To use it, add the HammerModule to the platform-browser import in the app.module.ts file and include it in the NgModule imports array.

import { HammerModule, BrowserModule } from '@angular/platform-browser';
import { BrowserAnimationsModule } from '@angular/platform-browser/animations';
@NgModule({
  declarations: [
    AppComponent
  ],
  imports: [
    BrowserAnimationsModule
    BrowserModule,
    HammerModule,
  ],
    bootstrap: [AppComponent]
})
export class AppModule {
}

You can also create a custom class to override specific features of HammerModule.

import { Injectable } from '@angular/core';
import { BrowserModule,
         HammerModule,
         HammerGestureConfig,
         HAMMER_GESTURE_CONFIG}
from '@angular/platform-browser';
@Injectable()
export class HammerConfig extends HammerGestureConfig {
    overrides = <any> {
       'pinch': {enable: true}
  }
}
@NgModule({
  declarations: [
    AppComponent
  ],
  imports: [
    BrowserAnimationsModule
    BrowserModule,
    HammerModule,
  ],
    providers:
    [{
      provide: HAMMER_GESTURE_CONFIG,
      useClass: HammerConfiguration
   }],
   bootstrap: [AppComponent]
})
export class AppModule {
}

The other values that can be overridden are:
pan, pinch, press, rotate, swipe and tap.
Note: that pinch and rotate are disabled by default.

Thanks for reading enjoy…


How To Use Typescripts …SpreadOperator In Angular

Blog post #004

Duncan Faulkner – February 2020

Recently I’ve found myself looking at ways I can improve the reading of my code. Just little things, just to tweak this or that just to make the code a little more easier on the eye.

Recently I’ve started using the spread operator (…) as I find this looks a lot cleaner and makes code easier to read.

In this post I’m going to show you how to use the spread operator in your Angular module files.

Most of the module files I’ve written have all looked the same. Starting at the top of the file are the Imports, followed by the NgModule with some or all of the following: Imports, Exports, Declarations, Entry Components and Providers.

Depending on the number of imports into the module this can have a lot of repeated code. So these files are ideal for the spread operator, reducing the repeated code. Lets see and example.

import { MatAutocompleteModule } from '@angular/material/autocomplete';
import { MatButtonModule } from '@angular/material/button';
import { MatButtonToggleModule } from '@angular/material/button-toggle';
import { MatCardModule } from '@angular/material/card';
import { MatCheckboxModule } from '@angular/material/checkbox';
import { MatChipsModule } from '@angular/material/chips';
import { NgModule } from '@angular/core';
@NgModule({
  imports: [
    MatAutocompleteModule,
    MatButtonModule,
    MatButtonToggleModule,
    MatCardModule,
    MatCheckboxModule,
    MatChipsModule
  ],
  declarations: [],
  exports: [
    MatAutocompleteModule,
    MatButtonModule,
    MatButtonToggleModule,
    MatCardModule,
    MatCheckboxModule,
    MatChipsModule
  ],
  entryComponents: [],
  providers: [MatIconRegistry]
})
export class MaterialModule { }

The above example is from the Mat-Icon material module file and shows the same things repeated three times in this example. Lets refactor this code.

import { MatAutocompleteModule } from '@angular/material/autocomplete';
import { MatButtonModule } from '@angular/material/button';
import { MatButtonToggleModule } from '@angular/material/button-toggle';
import { MatCardModule } from '@angular/material/card';
import { MatCheckboxModule } from '@angular/material/checkbox';
import { MatChipsModule } from '@angular/material/chips';
import { NgModule } from '@angular/core';
const MODULES = [
  MatAutocompleteModule,
  MatButtonModule,
  MatButtonToggleModule,
  MatCardModule,
  MatCheckboxModule,
  MatChipsModule
];
@NgModule({
  imports: [
    ...MODULES
  ],
  declarations: [],
  exports: [
   ...MODULES
  ],
  entryComponents: [],
  providers: [MatIconRegistry]
})
export class MaterialModule { }

Now, unfortunately the Angular Material imports need to be individually imported for each feature separately, but it does reduce the code (a bit) and it does help with having just one copy to manage, lets have another example, that I have changed to use the spread operator (you should be able to visualise what this looked like before).

import { NgModule } from '@angular/core';
import { VendorModule } from './vendor.module';
import {
  AnalysisComponent,
  ChartComponent,
  IconComponent,
  PodComponent,
  SpinnerComponent,
  TermsComponent,
  ChangePasswordComponent
} from './components';
import {
  PortPipe,
  NumericPipe,
  AsteriskPipe,
  SplitIpPipe,
  RemoveUnderscorePipe,
  AddUnderscorePipe,
  FormatBytesPipe
} from './pipes';
import { NumberDirective } from './directives';
const COMPONENTS = [
  AnalysisComponent,
  ChartComponent,
  IconComponent,
  PodComponent,
  SpinnerComponent,
  TermsComponent,
  ChangePasswordComponent
];
const PIPES = [
  PortPipe,
  NumericPipe,
  AsteriskPipe,
  SplitIpPipe,
  RemoveUnderscorePipe,
  AddUnderscorePipe,
  FormatBytesPipe
];
const DIRECTIVES = [NumberDirective];
const MODULES = [VendorModule];
const ENTRYCOMPONENTS = [TermsComponent, ChangePasswordComponent];
@NgModule({
  imports: [...MODULES],
  declarations: [...COMPONENTS, ...PIPES, ...DIRECTIVES],
  exports: [...COMPONENTS, ...PIPES, ...DIRECTIVES, ...MODULES],
  entryComponents: [...ENTRYCOMPONENTS],
  providers: [...PIPES]
})
export class SharedModule {}

In the example above, imports for the component, pipes and directives are all grouped together. Constants are configured, and used in the NgModule section of the file . The important part of this file is the NgModule section and you can easily now read that section without all the clutter of all the various components, pipes and directives all over the place.

The spread operator include options for object destructing, rest params and array destructing (as shown above).

Thanks for reading!


Previous Posts…