Creating UIs with Angular Material Design Components

Ahmed Bouchefra
Share

In this tutorial, I’ll introduce you to Material Design in Angular, then we’ll look at how to create a simple Angular application with a UI built from various Angular Material components.

The widespread adoption of component-based frameworks such as Angular, React and Vue.js has seen a growing number of pre-built UI component collections become available. Using such collections can help developers to quickly create professional-looking applications.

What’s Material Design?

Material Design (codenamed Quantum Paper) is a visual language that can be used to create digital experiences. It’s a set of principles and guidelines across platforms and devices for interactivity, motion and components that simplify the design workflow for teams designing their product.

The Material components allow you to create professional UIs with powerful modularity, theming and customization features.

Introduction to Angular Material

Angular Material is the implementation of Material Design principles and guidelines for Angular. It contains various UI components, such as:

  • form controls (input, select, checkbox, date picker and sliders etc.),
  • navigation patterns (menus, sidenav and toolbar)
  • layout components (grids, cards, tabs and lists )
  • buttons
  • indicators (progress bars and spinners)
  • popups and modals
  • data tables with headers and pagination etc.

Requirements

Before you can continue with this tutorial, you need to make sure you have a development machine with Node (6.9.0+) and NPM (3+) installed.

You also need to have the Angular CLI installed. If that’s not the case, simply run the following command in your terminal or command prompt:

npm install -g @angular/cli

Create the Project with the Angular CLI

Let’s now create the Angular project using the Angular CLI. Simply run the following command:

ng new angularmaterialdemo

You can then serve your application by running:

cd angularmaterialdemo
ng serve

The application will be running at http://localhost:4200.

Since we’re going to demonstrate different Angular Material components, we need to create a few Angular components and routing configurations for a simple demo application, so go ahead and use the CLI to generate the components:

ng g component login
ng g component CustomerList
ng g component CustomerCreate

Next, open src/app/app.module.ts and add the router configuration:

/*...*/

import { RouterModule, Routes } from  '@angular/router';

/*...*/

const  appRoutes:  Routes  = [
{ path:  'customer-list', component:  CustomerListComponent },
{ path:  'customer-create', component:  CustomerCreateComponent },
{
path:  'login',
component:  LoginComponent
},
{ path:  '',

redirectTo:  '/login',

pathMatch:  'full'

},
];

Getting Started with Angular Material

Now that we have a basic application, let’s get started by installing Angular Material and its different dependencies to enable the different features such as gestures and animations.

Installing Angular Material and Angular CDK

Let’s start by installing Angular Material and Angular CDK from npm.

Head back to your terminal and run the following command:

npm install --save @angular/material @angular/cdk

Adding HammerJS for Gestures Support

Components such as mat-slide-toggle, mat-slider and matTooltip require the HammerJS library for gestures support, so you need to install it for getting the full features of these components. Simply run the following command in your terminal:

npm install --save hammerjs

Next, open src/main.js (the entry point of your application) and import hammerjs

import 'hammerjs';

Adding a Theme

Angular Material has a bunch of pre-built themes. To use a theme, you simply need to import it in styles.css:

@import "~@angular/material/prebuilt-themes/indigo-pink.css";

You can find more information about theming in this guide.

Adding Angular Material Icons

Angular Material comes with a mat-icon component for icons, so you need to load the icon font before you can use it.

Add the following tag to your index.html file:

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

Using Animations with Angular Material Components

The last thing is enabling animations. Some components rely on the Angular animations module for advanced transitions, so you need to install the @angular/animations module and include the BrowserAnimationsModule in your application module configuration.

First, head back to your terminal and run the following command:

npm install --save @angular/animations

Next, open src/app/app.module.ts and add the following code:

/* ... */
import {BrowserAnimationsModule} from '@angular/platform-browser/animations';

@NgModule({
/*...*/
imports: [
BrowserModule,
BrowserAnimationsModule,
],
})
export  class  AppModule { }

Importing Angular Material Components

Before you can use any Angular Material component, you’ll have to import its module. Each component has its own module so you can include only the components you’re going to use.

Another approach is to create a separate module and import all the Angular Material components you need to use and then simply include this module in your application module.

So go ahead and create a src/app/material.module.ts file, and then add the following content:

import { NgModule } from  '@angular/core';
import {MatNativeDateModule,MatSnackBarModule,MatIconModule,MatDialogModule, MatButtonModule, MatTableModule, MatPaginatorModule , MatSortModule,MatTabsModule, MatCheckboxModule, MatToolbarModule, MatCard, MatCardModule, MatFormField, MatFormFieldModule, MatProgressSpinnerModule, MatInputModule } from  '@angular/material';
import {MatDatepickerModule} from  '@angular/material/datepicker';
import {MatRadioModule} from  '@angular/material/radio';
import {MatSelectModule} from  '@angular/material/select';
import {MatSliderModule} from  '@angular/material/slider';
import {MatDividerModule} from  '@angular/material/divider';

@NgModule({
imports: [MatTabsModule,MatDividerModule,MatSliderModule,MatSelectModule,MatRadioModule,MatNativeDateModule,MatDatepickerModule,MatSnackBarModule,MatIconModule,MatDialogModule,MatProgressSpinnerModule,MatButtonModule,MatSortModule,MatTableModule,MatTabsModule, MatCheckboxModule, MatToolbarModule, MatCardModule, MatFormFieldModule, MatProgressSpinnerModule, MatInputModule, MatPaginatorModule],
exports: [MatTabsModule,MatDividerModule,MatSliderModule,MatSelectModule,MatRadioModule,MatNativeDateModule,MatDatepickerModule,MatSnackBarModule,MatIconModule,MatDialogModule,MatProgressSpinnerModule,MatButtonModule,MatSortModule, MatCheckboxModule, MatToolbarModule, MatCardModule,MatTableModule,MatTabsModule, MatFormFieldModule, MatProgressSpinnerModule, MatInputModule, MatPaginatorModule],

})

export  class  MyMaterialModule { }

Next, include this module in src/app/app.module.ts:

import { MyMaterialModule } from  './material.module';

/*...*/

@NgModule({

/*...*/

imports: [

/*...*/
MyMaterialModule,
],
/*...*/
})

export  class  AppModule { }

That’s it: you can now use the imported Angular Material components in your Angular application.

Create the UI for the Application

You’ve previously created the application components and added the router configuration. Now let’s build the UI for the different components using Angular Material components.

Building the UI for AppComponent

Go ahead and open src/app/app.component.html, then add:

  • a Material toolbar with three Material buttons (mat-button) for links to the app components
  • a router outlet <router-outlet> where the components matching a router path will be inserted.

This is the HTML code for the component:

<mat-toolbar  color="primary"  class="fixed-header">
<mat-toolbar-row>
<span><img  src="./assets/logo.png"  style="width:50px;"></span>
<a  mat-button  routerLink="/customer-list"  routerLinkActive="active">Customers</a>
<a  mat-button  routerLink="/customer-create">Create Customer</a>
<a  mat-button  routerLink="/login">Login</a>
</mat-toolbar-row>
</mat-toolbar>
<main>
<router-outlet  style="margin-top: 80px;"></router-outlet>
</main>

By adding some styling, this is how the toolbar looks:

Angular Material: a first look at the toolbar

To create a toolbar, you use the <mat-toolbar> component and then you create one or multiple rows inside the toolbar using the the <mat-toolbar-row> component.

Notice that we have imported the MatToolbarModule and MatButtonModule modules from @angular/material in the material.module.ts.

You can also make use of other navigation components such as menus and sidebars.

Building the UI for the Login Component

After creating the UI for the root component of our application, let’s create the UI for the login component.

Open src/app/login/login.component.html and add the following HTML code. Add an Angular Material card to contain the login form:

<mat-card  class="login-card">
<mat-card-header>
    <mat-card-title>Login</mat-card-title>
</mat-card-header>
    <mat-card-content>
    <!-- The form goes here -->
    </mat-card-content>

A <mat-card> component serves as a single-object container for text, photos, and actions. Find more details about cards from the docs.

Next in the content section of the card, add the HTML form:

    <form  class="login-form">
        <!-- Form elements are here -->
    </form>

Now let’s add two inputs to get the user’s username and password credentials:

<mat-form-field>
<input  matInput  placeholder="Username" [(ngModel)]="username"  name="username"  required>
</mat-form-field>
<mat-form-field>
<input  matInput  placeholder="Password" [(ngModel)]="password"  type="password"  name="password"  required>
</mat-form-field>

Many Angular Material components need to be wrapped inside a component to have common Text field styles such as the underline, floating label, and hint messages.

These are the components that are designed to be wrapped inside <mat-form-field>:

Finally, in the actions section of the card, let’s add an Angular Material button for the login action:

<mat-card-actions>
    <button  mat-raised-button (click)="login()"  color="primary">Login</button>
</mat-card-actions>
</mat-card>

To create an Angular Material button, you simply use native HTML <button> and <a> elements and add Material attributes such as mat-button and mat-raised-button. For more details, check the docs.

This is how the login form looks:

The login form

If the login is successful (username: demo, password: demo) the user will be redirected to the CustomerList component. Otherwise, the user will get an error message: “Your login information are incorrect!”

Create the Error Modal Dialog

MatDialog can be used to create and open modal dialogs. The dialog requires a component to be loaded, so first create an Angular component that displays the error message that gets passed.

Create src/app/error.component.ts, then add the following code:

import {Component, Inject, Injectable} from  '@angular/core';

import {MatDialogRef, MAT_DIALOG_DATA, MatDialog} from  '@angular/material';

@Component({
templateUrl:  'error.component.html'
})
export  class  ErrorComponent {
    constructor(private  dialogRef:  MatDialogRef<ErrorComponent>, @Inject(MAT_DIALOG_DATA) public  data:  any) {
    }
    public  closeDialog() {
        this.dialogRef.close();
    }
}

This component will be created and opened with MatDialog so it can inject MatDialogRef, which provides a handle on the opened dialog and can be used to close the dialog and receive notifications when the dialog gets closed. (The closeDialog() method uses this reference to close the dialog.)

Our error component needs a way to get the error message to be displayed for the user. (The message will be passed when the component is opened using MatDialog open method.) You can access the data in your dialog component using the MAT_DIALOG_DATA injection token that can be imported from '@angular/material' and then injected into the component using @Inject().

Next, create src/app/error.component.html and add the following code:

<h2  mat-dialog-title>Error</h2>
<mat-dialog-content  class="error">{{data.message}}</mat-dialog-content>
<mat-dialog-actions>
<button  mat-raised-button (click)="closeDialog()">Ok</button>
</mat-dialog-actions>

We’re using many available directives for structuring the dialog components:

  • mat-dialog-title: used for the dialog title, and needs to be applied to heading elements <h1>, <h2> etc.
  • <mat-dialog-content>: used for the scrollable content of the dialog
  • <mat-dialog-actions>: used as a container for dialog actions.

Next, open src/app/app.module.ts and add this component to the module:

@NgModule({

declarations: [

/*...*/
    ErrorComponent
],

imports: [/*...*/
],
entryComponents: [ErrorComponent],
/*...*/
})

export  class  AppModule { }

Now let’s add the login() method (to trigger the error dialog) to the LoginComponent:

import { Component, OnInit } from  '@angular/core';
import { MatDialog, MatDialogRef } from  '@angular/material';

/* ... */

@Component({
/* ... */
})
export  class  LoginComponent{

    public  username:  string  =  "";
    public  password:  string  =  "";


    constructor(private  dialog:  MatDialog, private  router:  Router) { }
    login(){
        if(this.username  ===  "demo"  &&  this.password  === "demo")
        {
            this.router.navigate(['customer-list']);
        }
        else
        {
            this.dialog.open(ErrorComponent,{ data: {
            message:  "Your login information are incorrect!"
            }});
        }
    }
}

We simply inject the MatDialog component and use it to open a dialog (if the user credentials aren’t correct) with the ErrorComponent component and a config object holding the data that will be passed. The open() method returns an instance of MatDialogRef.

This is a screenshot of the error dialog:

A screenshot of the error dialog

You can find more information about dialogs from the docs.

Building the UI for the CustomerList Component

For the CustomerList component we’ll make use of different Angular Material components, most importantly:

Open src/app/customer-list/customer-list.component.html and add the following markup:

<div  class="mat-elevation-z8">
<!-- Other contents here -->
</div>

the mat-elevation-z8 class is an Angular Material elevation class that allows you to add separation between elements along the z-axis. You can find more details from this link.

Adding a Loading Spinner

We use <mat-spinner> to display a spinner when data is still loading, which we’re simulating using a setTimeout() function:

<mat-spinner [style.display]="loading ? 'block' : 'none'"></mat-spinner>

If the loading variable equates to true, the CSS display property gets assigned the block value. Otherwise, it gets assigned none which makes it disappear.

You need to add a loading variable to the component class, which initially takes a value of true.

    loading  =  true;
    /*...*/
    constructor() {}
ngOnInit():  void {

    setTimeout(() => {

        this.loading  =  false;

    }, 2000);

    /*...*/

Adding a Data Table with a Data Source

We then create a Material data table using <mat-table> that gets displayed only if loading equals false:

<mat-table [style.display]="loading === false ? 'block' : 'none'" #table [dataSource]="dataSource">
<!-- Other contents here -->
</mat-table>

The data table takes a dataSource property that’s used to provide data for the table. You can simply pass a data array to the table, but for real-world use cases you need to use an advanced data source such as MatTableDataSource, provided by Angular Material, that encapsulates logic for advanced operations such as pagination, sorting and filtering:

export  class  CustomerListComponent  implements  OnInit {

    customers:  Customer[] = [
    { id:1, name:'Customer 001',job:'Programmer'},
    /*...*/
    ];

    dataSource  =  new  MatTableDataSource<Customer>(this.customers);

Adding Column Templates

For each column definition, you need to provide a unique name and the content for its header and row cells. For example, this is the column for displaying the customer ID:

<ng-container  matColumnDef="id">
<mat-header-cell *matHeaderCellDef> CustomerId </mat-header-cell>
<mat-cell *matCellDef="let customer"> {{customer.id}} </mat-cell>
</ng-container>

This is a simple column definition with the name id. The header cell contains the text CustomerId and each row cell will render the id property of each row’s data.

In a similar way, you can add the other column definitions.

For the operations column, we’re using two Angular Material icons: delete and edit wrapped with Material buttons:

<ng-container  matColumnDef="operations">

<mat-header-cell *matHeaderCellDef  mat-sort-header> Operations </mat-header-cell>

<mat-cell *matCellDef="let element">  <button  mat-button  color="primary" (click)="deleteCustomer(element.id)"><mat-icon>delete</mat-icon>

</button>  <button  mat-button  color="primary" (click)="editCustomer(element.id)"><mat-icon>edit</mat-icon></button>  </mat-cell>

</ng-container>

Adding Row Templates

After defining column templates, you need to define the row templates, so first you need to add a variable that contains the list of columns you have to the component:

displayedColumns  = ['id', 'name', 'job', 'operations'];

Next you need to add <mat-header-row> and <mat-row> to the content of your <mat-table> and provide your column list as inputs:

<mat-header-row *matHeaderRowDef="displayedColumns"></mat-header-row>
<mat-row *matRowDef="let row; columns: displayedColumns;"></mat-row>

Adding Pagination

You can add pagination to your data table by simply adding a <mat-paginator> component after <mat-table>:

<mat-paginator #paginator [style.display]="loading === false ? 'block' : 'none'" [pageSize]="5"></mat-paginator>

One benefit of using MatTableDataSource is that you get pagination out of the box by simply providing MatPaginator to your data source.

First make sure you import the MatPaginatorModule in material.module.ts. Then you need to get the paginator directive:

@ViewChild(MatPaginator) paginator:  MatPaginator;

Finally, you just pass the paginator to the table’s data source:

ngAfterViewInit() {
    this.dataSource.paginator  =  this.paginator;
}

Adding Sorting

Using MatTableDataSource you can also have sorting out of the box by only adding a few things.

First, make sure you have MatSortModule imported in your module (material.module.ts).

Next, add the matSort directive to the <mat-table> component and then add mat-sort-header to each column header cell that needs to have sorting.

For example, let’s add sorting to the name column:

<ng-container  matColumnDef="name">
<mat-header-cell *matHeaderCellDef  mat-sort-header> Name </mat-header-cell>
<mat-cell *matCellDef="let customer"> {{customer.name}} </mat-cell>
</ng-container>

Finally, you need to provide the MatSort directive to the data source and it will automatically have sorting:

import {MatTableDataSource, MatPaginator, MatSort} from  '@angular/material';
/*...*/
export  class  CustomerListComponent  implements  OnInit {

@ViewChild(MatSort) sort:  MatSort;

ngAfterViewInit() {
    /*...*/
    this.dataSource.sort  =  this.sort;
}

You can also add filtering and selection to your table. Check the docs for more information.

Using MatTableDataSource provides you with many built-in features out of the box, but it only supports client-side pagination. For server-side pagination or other custom features, you need to create your custom data source.

In the case of a custom data source, you’ll need to listen to the paginator’s (page) event to implement pagination and to the sort’s (matSortChange) event for implementing data sorting.

This is a screenshot of the CustomerList component:

The CustomerList component

Adding SnackBar Notifications

First make sure you’ve imported MatSnackBarModule into your module. Next, import and inject MatSnackBar into your component, then simply call the open() method of the MatSnackBar instance:

import {MatSnackBar} from  '@angular/material';
/*...*/
constructor(public  snackBar:  MatSnackBar) {}

deleteCustomer(id){
    let  snackBarRef  =  this.snackBar.open(`Deleting customer #${id}`);
}
editCustomer(id){
    let  snackBarRef  =  this.snackBar.open(`Editing customer #${id}`);
}

Building the UI for the CustomerCreate Component

For the CustomerCreate component, we’ll use a bunch of Angular Material components, such as:

  • the card component (<mat-card>)
  • the tabs component (<mat-tab> and <mat-tab-group>)
  • the label (<mat-label>) and input (<input matInput> and <textarea matInput>) components
  • the form field (<mat-form-field>) component
  • the checkbox (<mat-checkbox>) component
  • the date picker (<mat-datepicker>) component
  • the radio button (<mat-radio-button) component
  • the select (<mat-select>) component.

So open src/app/customer-create/customer-create.component.html and start by adding the card component that holds the customer creation form:

<mat-card  class="my-card">
<mat-card-header>
    <mat-card-title>Create Customer</mat-card-title>
</mat-card-header>

    <mat-card-content>
    <!-- The form goes here -->
    </mat-card-content>
    <mat-card-actions>
    <!-- Actions go here -->
    </mat-card-actions>
</mat-card>

In the card content section, let’s add an HTML form:

<form  class="my-form">
<!-- Form fields here -->
</form>

Next, let’s organize the form fields into horizontal tabs using the Angular Material tabs components. Inside the form element, add the following code to create two tabs with General Information and Other Information labels:

<mat-tab-group>
<mat-tab  label="General Information"  class="my-tab">
<!-- Form fields here -->
</mat-tab>
<mat-tab  label="Other Information"  class="my-tab">
<!-- Form fields here -->
</mat-tab>
</mat-tab-group>

In each tab, we’ll add an HTML table to organize the form fields into table cells:

<table  style="width: 100%"  cellspacing="10">
<tr>
    <td> <!-- Form fields here --> </td>
</tr>
</table>

Add the following content for the first tab. Inside the table row/cell, add a field for the customer name:

<mat-form-field>
    <mat-label>Name</mat-label>
    <input  matInput  placeholder="Name" [(ngModel)]="name"  name="name"  required>
</mat-form-field>

Next create an input for the customer address:

<mat-form-field>
    <mat-label>Address</mat-label>
    <textarea [(ngModel)]="address"  matInput></textarea>
</mat-form-field>

Finally, create an input field for the customer email:

<mat-form-field>
    <mat-label>Email</mat-label>
    <input  matInput  placeholder="Email" [(ngModel)]="email"  name="email">
</mat-form-field>

We’re using <mat-label> to create labels, <input matInput /> to create an input field, and <textarea matInput> to create a text area field.

For the second tab, add the following content. In the first row/cell of the table, add the Is company? checkbox:

<mat-checkbox [checked]="isCompany">Is company?</mat-checkbox>

The checked property determines if the checkbox is checked or not.

Next, add a date input with a date picker wrapped inside an Angular Material form field:

<mat-form-field>
    <mat-label>Created At</mat-label>
    <input  [value]="createdAt.value" matInput [matDatepicker]="picker"  placeholder="Date of creation">
    <mat-datepicker-toggle  matSuffix [for]="picker"></mat-datepicker-toggle>
    <mat-datepicker #picker></mat-datepicker>
</mat-form-field>

As explained in the Angular mMaterial docs:

The datepicker allows users to enter a date either through text input, or by choosing a date from the calendar. It is made up of several components and directives that work together.

Next, add a group of radio buttons to choose the gender of the customer:

<mat-label>Gender</mat-label>
<mat-radio-group [(value)]="selectedGender">
    <mat-radio-button  value="male">Male</mat-radio-button>
    <mat-radio-button  value="female">Female</mat-radio-button>
</mat-radio-group>

To get the currently selected radio button inside the group, simply bind a variable to the value property.

Finally, add a select field to select the source of the lead/customer:

<mat-form-field>
    <mat-label>Lead Source</mat-label>
        <mat-select [(value)]="selectedSource" placeholder="Source">
        <mat-option>None</mat-option>
        <mat-option value="email">Email Marketing</mat-option>
        <mat-option value="social">Social Media</mat-option>
        <mat-option value="affiliate">Affiliate</mat-option>
    </mat-select>
</mat-form-field>

The <mat-select> component supports two-way binding to the value property without the need for Angular forms.

Now let’s add a button to create the customer from the form information:

<mat-card-actions>
    <button  mat-raised-button (click)="createCustomer()"  color="primary">Create</button>
</mat-card-actions>

Now you need to create variables that are bound to these controls. In src/app/customer-create/customer-create.component.ts, add the following code to the component class:

name:  string  =  "";
email:  string  =  "";
job:  string  =  "";
address:  string  =  "";
selectedSource:  string  =  "email";
selectedGender:  string  =  "male";
isCompany  :  boolean  =  false;
createdAt  =  new  FormControl(new  Date());
public  createCustomer(){
/* logic to create a customer from the form information*/
}

This is a screenshot of the component UI:

The component UI

You can see a live demo of this here.

Conclusion

With internationalization, accessibility, freedom from bugs and high performance, Angular Material aims to build a set of high-quality UI components using Angular and based on the Material design specification.

In this tutorial, we’ve built a simple UI using Angular Material with a variety of UI components. You can find the complete set of available Angular Material components from the Angular Material docs.

You can find the source code of the demo we’ve built throughout this tutorial in this GitHub repository.