36. Angular Material
🎨 Angular Material
Заголовок раздела «🎨 Angular Material»Angular Material — официальная библиотека UI-компонентов от команды Angular, реализующая Material Design от Google. Это не просто набор красивых кнопок — это полноценная дизайн-система с доступностью (a11y), темизацией и CDK под капотом 💅
Установка
Заголовок раздела «Установка»ng add @angular/materialКоманда автоматически:
- Установит
@angular/materialи@angular/cdk - Добавит тему в
styles.scss - Настроит анимации
- Добавит шрифты Material Icons
Подключение компонентов (Standalone)
Заголовок раздела «Подключение компонентов (Standalone)»В Angular 17+ каждый компонент Material — это standalone:
import { Component } from '@angular/core';import { MatButtonModule } from '@angular/material/button';import { MatCardModule } from '@angular/material/card';import { MatInputModule } from '@angular/material/input';import { MatFormFieldModule } from '@angular/material/form-field';import { MatTableModule } from '@angular/material/table';import { MatDialogModule } from '@angular/material/dialog';import { MatSnackBarModule } from '@angular/material/snack-bar';import { MatSelectModule } from '@angular/material/select';import { MatDatepickerModule } from '@angular/material/datepicker';import { MatAutocompleteModule } from '@angular/material/autocomplete';import { MatIconModule } from '@angular/material/icon';import { MatToolbarModule } from '@angular/material/toolbar';import { MatSidenavModule } from '@angular/material/sidenav';import { MatProgressSpinnerModule } from '@angular/material/progress-spinner';import { MatChipsModule } from '@angular/material/chips';import { MatBadgeModule } from '@angular/material/badge';
@Component({ standalone: true, imports: [ MatButtonModule, MatCardModule, MatInputModule, MatFormFieldModule, // ... и т.д. ]})export class DashboardComponent {}Кнопки (MatButton)
Заголовок раздела «Кнопки (MatButton)»<!-- Варианты кнопок --><button mat-button>Text</button><button mat-raised-button>Raised</button><button mat-stroked-button>Stroked</button><button mat-flat-button>Flat</button><button mat-icon-button><mat-icon>favorite</mat-icon></button><button mat-fab><mat-icon>add</mat-icon></button><button mat-mini-fab><mat-icon>edit</mat-icon></button>
<!-- Цвета --><button mat-raised-button color="primary">Primary</button><button mat-raised-button color="accent">Accent</button><button mat-raised-button color="warn">Warn</button>
<!-- С иконкой --><button mat-raised-button color="primary"> <mat-icon>save</mat-icon> Сохранить</button>Поля ввода (MatInput / MatFormField)
Заголовок раздела «Поля ввода (MatInput / MatFormField)»<!-- Базовый input --><mat-form-field appearance="outline"> <mat-label>Email</mat-label> <input matInput type="email" [formControl]="emailControl" /> <mat-icon matSuffix>email</mat-icon> <mat-error *ngIf="emailControl.hasError('required')"> Email обязателен </mat-error> <mat-error *ngIf="emailControl.hasError('email')"> Некорректный email </mat-error></mat-form-field>
<!-- Textarea --><mat-form-field appearance="outline"> <mat-label>Описание</mat-label> <textarea matInput cdkTextareaAutosize rows="3"></textarea></mat-form-field>
<!-- Select --><mat-form-field appearance="outline"> <mat-label>Город</mat-label> <mat-select [formControl]="cityControl"> <mat-option value="moscow">Москва</mat-option> <mat-option value="spb">Санкт-Петербург</mat-option> <mat-option value="ekb">Екатеринбург</mat-option> </mat-select></mat-form-field>Диалог (MatDialog)
Заголовок раздела «Диалог (MatDialog)»import { MatDialogRef, MAT_DIALOG_DATA, MatDialogModule } from '@angular/material/dialog';import { inject } from '@angular/core';
interface DialogData { title: string; message: string;}
@Component({ standalone: true, imports: [MatDialogModule, MatButtonModule], template: ` <h2 mat-dialog-title>{{ data.title }}</h2> <mat-dialog-content>{{ data.message }}</mat-dialog-content> <mat-dialog-actions align="end"> <button mat-button mat-dialog-close>Отмена</button> <button mat-raised-button color="warn" [mat-dialog-close]="true"> Удалить </button> </mat-dialog-actions> `})export class ConfirmDialogComponent { data = inject<DialogData>(MAT_DIALOG_DATA); dialogRef = inject(MatDialogRef<ConfirmDialogComponent>);}
// Открытие диалога@Component({...})export class ListComponent { private dialog = inject(MatDialog);
openConfirm(item: Item) { const ref = this.dialog.open(ConfirmDialogComponent, { width: '400px', data: { title: 'Удалить?', message: \`Удалить "\${item.name}"?\` } });
ref.afterClosed().subscribe(confirmed => { if (confirmed) this.deleteItem(item.id); }); }}Snackbar (MatSnackBar)
Заголовок раздела «Snackbar (MatSnackBar)»@Injectable({ providedIn: 'root' })export class NotificationService { private snackBar = inject(MatSnackBar);
success(message: string) { this.snackBar.open(message, '✕', { duration: 3000, panelClass: ['snack-success'], horizontalPosition: 'end', verticalPosition: 'top', }); }
error(message: string) { this.snackBar.open(message, 'Закрыть', { duration: 5000, panelClass: ['snack-error'], }); }}Таблица (MatTable)
Заголовок раздела «Таблица (MatTable)»@Component({ standalone: true, imports: [MatTableModule, MatSortModule, MatPaginatorModule], template: ` <table mat-table [dataSource]="dataSource" matSort>
<!-- Колонка ID --> <ng-container matColumnDef="id"> <th mat-header-cell *matHeaderCellDef mat-sort-header>ID</th> <td mat-cell *matCellDef="let row">{{ row.id }}</td> </ng-container>
<!-- Колонка Name --> <ng-container matColumnDef="name"> <th mat-header-cell *matHeaderCellDef mat-sort-header>Имя</th> <td mat-cell *matCellDef="let row">{{ row.name }}</td> </ng-container>
<!-- Колонка Actions --> <ng-container matColumnDef="actions"> <th mat-header-cell *matHeaderCellDef></th> <td mat-cell *matCellDef="let row"> <button mat-icon-button (click)="edit(row)"> <mat-icon>edit</mat-icon> </button> <button mat-icon-button color="warn" (click)="delete(row)"> <mat-icon>delete</mat-icon> </button> </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 /> `})export class UsersTableComponent implements AfterViewInit { @ViewChild(MatSort) sort!: MatSort; @ViewChild(MatPaginator) paginator!: MatPaginator;
displayedColumns = ['id', 'name', 'actions']; dataSource = new MatTableDataSource(USERS);
ngAfterViewInit() { this.dataSource.sort = this.sort; this.dataSource.paginator = this.paginator; }
applyFilter(event: Event) { const filter = (event.target as HTMLInputElement).value; this.dataSource.filter = filter.trim().toLowerCase(); }}DatePicker и Autocomplete
Заголовок раздела «DatePicker и Autocomplete»<!-- DatePicker --><mat-form-field appearance="outline"> <mat-label>Дата рождения</mat-label> <input matInput [matDatepicker]="picker" [formControl]="dateControl" /> <mat-hint>ДД.ММ.ГГГГ</mat-hint> <mat-datepicker-toggle matIconSuffix [for]="picker" /> <mat-datepicker #picker /></mat-form-field>
<!-- Autocomplete --><mat-form-field appearance="outline"> <mat-label>Поиск города</mat-label> <input matInput [formControl]="searchControl" [matAutocomplete]="auto" /> <mat-autocomplete #auto="matAutocomplete" [displayWith]="displayFn"> @for (option of filteredOptions$ | async; track option.id) { <mat-option [value]="option">{{ option.name }}</mat-option> } </mat-autocomplete></mat-form-field>Темизация с CSS Variables (Angular Material 3)
Заголовок раздела «Темизация с CSS Variables (Angular Material 3)»@use '@angular/material' as mat;
// Включаем Material 3 (MDC)@include mat.core();
// Создаём тему через CSS переменные:root { --mat-sys-primary: #dd0031; --mat-sys-on-primary: #ffffff; --mat-sys-primary-container: #ffd8d8; --mat-sys-surface: #0f172a; --mat-sys-on-surface: #e2e8f0; --mat-sys-background: #0f172a;}
// Кастомная палитра$my-theme: mat.define-theme(( color: ( theme-type: dark, primary: mat.$red-palette, tertiary: mat.$blue-palette, ), typography: ( brand-family: 'Inter, sans-serif', ), density: ( scale: 0, )));
html { @include mat.all-component-themes($my-theme);}Кастомные компоненты Material
Заголовок раздела «Кастомные компоненты Material»// Своя карточка-статистика@Component({ standalone: true, imports: [MatCardModule, MatIconModule], template: ` <mat-card class="stat-card" [class.highlight]="highlight"> <mat-card-header> <mat-icon mat-card-avatar [style.color]="color">{{ icon }}</mat-icon> <mat-card-title>{{ value }}</mat-card-title> <mat-card-subtitle>{{ label }}</mat-card-subtitle> </mat-card-header> <mat-card-content> <ng-content /> </mat-card-content> </mat-card> `, styles: [` .stat-card { transition: box-shadow 0.3s; cursor: pointer; &:hover { box-shadow: 0 8px 24px rgba(221, 0, 49, 0.2); } } .stat-card.highlight { border-left: 4px solid #dd0031; } `]})export class StatCardComponent { @Input() icon = 'analytics'; @Input() value = '0'; @Input() label = ''; @Input() color = '#dd0031'; @Input() highlight = false;}