What is Angular Material?
Angular Material is a UI component library that implements Google's Material Design for Angular applications. It provides:
- 35+ pre-built, accessible components
- Built-in theming system
- Responsive layouts with CDK
- Consistent design language
Installation
bash
ng add @angular/materialThis command will:
- Install
@angular/materialand@angular/cdk - Let you choose a prebuilt theme
- Set up global typography and animations
- Add the Roboto font and Material Icons
Importing Components
Import components individually in your standalone components:
typescript
import { MatButtonModule } from '@angular/material/button';
import { MatCardModule } from '@angular/material/card';
import { MatIconModule } from '@angular/material/icon';
@Component({
selector: 'app-dashboard',
standalone: true,
imports: [MatButtonModule, MatCardModule, MatIconModule],
template: `...`,
})
export class DashboardComponent {}Common Components
Buttons
html
<!-- Basic buttons -->
<button mat-button>Basic</button>
<button mat-raised-button>Raised</button>
<button mat-flat-button>Flat</button>
<button mat-stroked-button>Stroked</button>
<!-- Colored buttons -->
<button mat-raised-button color="primary">Primary</button>
<button mat-raised-button color="accent">Accent</button>
<button mat-raised-button color="warn">Warn</button>
<!-- Icon button -->
<button mat-icon-button>
<mat-icon>favorite</mat-icon>
</button>
<!-- FAB -->
<button mat-fab>
<mat-icon>add</mat-icon>
</button>Cards
html
<mat-card>
<mat-card-header>
<mat-card-title>Angular Material</mat-card-title>
<mat-card-subtitle>UI Component Library</mat-card-subtitle>
</mat-card-header>
<img mat-card-image src="angular.png" alt="Angular">
<mat-card-content>
<p>Build beautiful Angular apps with Material Design components.</p>
</mat-card-content>
<mat-card-actions align="end">
<button mat-button>LEARN MORE</button>
<button mat-raised-button color="primary">GET STARTED</button>
</mat-card-actions>
</mat-card>Forms / Inputs
html
<mat-form-field appearance="outline">
<mat-label>Email</mat-label>
<input matInput type="email" [formControl]="emailControl">
<mat-icon matSuffix>email</mat-icon>
<mat-hint>Enter your email address</mat-hint>
<mat-error>Please enter a valid email</mat-error>
</mat-form-field>
<mat-form-field appearance="outline">
<mat-label>Country</mat-label>
<mat-select [formControl]="countryControl">
<mat-option value="us">United States</mat-option>
<mat-option value="uk">United Kingdom</mat-option>
<mat-option value="ca">Canada</mat-option>
</mat-select>
</mat-form-field>Toolbar and Sidenav
html
<mat-sidenav-container>
<mat-sidenav mode="side" opened>
<mat-nav-list>
<a mat-list-item routerLink="/dashboard" routerLinkActive="active">
<mat-icon matListItemIcon>dashboard</mat-icon>
<span matListItemTitle>Dashboard</span>
</a>
<a mat-list-item routerLink="/users" routerLinkActive="active">
<mat-icon matListItemIcon>people</mat-icon>
<span matListItemTitle>Users</span>
</a>
</mat-nav-list>
</mat-sidenav>
<mat-sidenav-content>
<mat-toolbar color="primary">
<span>My Application</span>
<span class="spacer"></span>
<button mat-icon-button><mat-icon>notifications</mat-icon></button>
<button mat-icon-button><mat-icon>account_circle</mat-icon></button>
</mat-toolbar>
<router-outlet></router-outlet>
</mat-sidenav-content>
</mat-sidenav-container>Table
typescript
@Component({
standalone: true,
imports: [MatTableModule, MatSortModule, MatPaginatorModule],
template: `
<table mat-table [dataSource]="dataSource" matSort>
<ng-container matColumnDef="name">
<th mat-header-cell *matHeaderCellDef mat-sort-header>Name</th>
<td mat-cell *matCellDef="let user">{{ user.name }}</td>
</ng-container>
<ng-container matColumnDef="email">
<th mat-header-cell *matHeaderCellDef mat-sort-header>Email</th>
<td mat-cell *matCellDef="let user">{{ user.email }}</td>
</ng-container>
<ng-container matColumnDef="role">
<th mat-header-cell *matHeaderCellDef>Role</th>
<td mat-cell *matCellDef="let user">
<mat-chip [color]="user.role === 'admin' ? 'primary' : 'default'">
{{ user.role }}
</mat-chip>
</td>
</ng-container>
<tr mat-header-row *matHeaderRowDef="displayedColumns"></tr>
<tr mat-row *matRowDef="let row; columns: displayedColumns;"></tr>
</table>
<mat-paginator [pageSizeOptions]="[5, 10, 25]" showFirstLastButtons></mat-paginator>
`,
})
export class UserTableComponent implements AfterViewInit {
displayedColumns = ['name', 'email', 'role'];
dataSource = new MatTableDataSource<User>([]);
@ViewChild(MatSort) sort!: MatSort;
@ViewChild(MatPaginator) paginator!: MatPaginator;
ngAfterViewInit() {
this.dataSource.sort = this.sort;
this.dataSource.paginator = this.paginator;
}
}Dialog
typescript
// dialog component
@Component({
standalone: true,
imports: [MatDialogModule, MatButtonModule],
template: `
<h2 mat-dialog-title>Confirm Delete</h2>
<mat-dialog-content>
Are you sure you want to delete "{{ data.name }}"?
</mat-dialog-content>
<mat-dialog-actions align="end">
<button mat-button mat-dialog-close>Cancel</button>
<button mat-raised-button color="warn" [mat-dialog-close]="true">Delete</button>
</mat-dialog-actions>
`,
})
export class ConfirmDialogComponent {
data = inject(MAT_DIALOG_DATA);
}
// Open from another component
export class UserListComponent {
private dialog = inject(MatDialog);
confirmDelete(user: User) {
const ref = this.dialog.open(ConfirmDialogComponent, {
data: { name: user.name },
width: '400px',
});
ref.afterClosed().subscribe(confirmed => {
if (confirmed) this.deleteUser(user.id);
});
}
}Snackbar (Toast Notifications)
typescript
export class NotificationService {
private snackBar = inject(MatSnackBar);
success(message: string) {
this.snackBar.open(message, 'Close', {
duration: 3000,
panelClass: 'snack-success',
horizontalPosition: 'end',
verticalPosition: 'top',
});
}
error(message: string) {
this.snackBar.open(message, 'Dismiss', {
duration: 5000,
panelClass: 'snack-error',
});
}
}Theming
Custom Theme
Create a custom theme in styles.scss:
scss
@use '@angular/material' as mat;
// Define custom palettes
$my-primary: mat.m2-define-palette(mat.$m2-indigo-palette);
$my-accent: mat.m2-define-palette(mat.$m2-pink-palette, A200, A100, A400);
$my-warn: mat.m2-define-palette(mat.$m2-red-palette);
// Create theme
$my-theme: mat.m2-define-light-theme((
color: (
primary: $my-primary,
accent: $my-accent,
warn: $my-warn,
),
typography: mat.m2-define-typography-config(),
density: 0,
));
// Apply theme globally
@include mat.all-component-themes($my-theme);
// Dark theme variant
.dark-theme {
$dark-theme: mat.m2-define-dark-theme((
color: (
primary: $my-primary,
accent: $my-accent,
warn: $my-warn,
),
));
@include mat.all-component-colors($dark-theme);
}Theme Toggle
typescript
@Component({
template: `
<button mat-icon-button (click)="toggleTheme()">
<mat-icon>{{ isDark ? 'light_mode' : 'dark_mode' }}</mat-icon>
</button>
`,
})
export class ThemeToggleComponent {
isDark = false;
toggleTheme() {
this.isDark = !this.isDark;
document.body.classList.toggle('dark-theme', this.isDark);
}
}CDK (Component Dev Kit)
The CDK provides behavior primitives without styling:
| Feature | Use Case |
|---|---|
DragDropModule | Drag and drop lists |
OverlayModule | Custom popups, tooltips |
ScrollingModule | Virtual scrolling for large lists |
A11yModule | Focus traps, live announcer |
ClipboardModule | Copy to clipboard |
LayoutModule | Responsive breakpoint detection |
typescript
// Virtual scrolling for performance
import { ScrollingModule } from '@angular/cdk/scrolling';
@Component({
imports: [ScrollingModule],
template: `
<cdk-virtual-scroll-viewport itemSize="48" class="list-container">
<div *cdkVirtualFor="let item of items" class="list-item">
{{ item.name }}
</div>
</cdk-virtual-scroll-viewport>
`,
styles: [`.list-container { height: 400px; }`],
})
export class VirtualListComponent {
items = Array.from({ length: 10000 }, (_, i) => ({ name: `Item ${i}` }));
}Next Steps
With polished UIs using Angular Material, let's learn about testing your Angular application — unit tests, component tests, and integration tests using Jasmine and Karma.