Angular IDPStrong Junior
Модулі / Грид / Angular Material Table
🔥 0

Модуль 5 · урок 2 з 10

Angular Material Table

Material Table — це не готовий грид, а конструктор. Він дає розмітку, сортування й пагінацію, але верстку рядків і логіку даних ти пишеш сам. Для навчального проєкту це плюс: видно, що відбувається всередині.

Мінімальна таблиця

import { MatTableModule } from '@angular/material/table';

@Component({
  imports: [MatTableModule],
  template: `
    <table mat-table [dataSource]="rows()" class="grid">

      <ng-container matColumnDef="code">
        <th mat-header-cell *matHeaderCellDef>Код</th>
        <td mat-cell *matCellDef="let row">{{ row.code }}</td>
      </ng-container>

      <ng-container matColumnDef="title">
        <th mat-header-cell *matHeaderCellDef>Назва</th>
        <td mat-cell *matCellDef="let row">{{ row.title }}</td>
      </ng-container>

      <tr mat-header-row *matHeaderRowDef="columns; sticky: true"></tr>
      <tr mat-row *matRowDef="let row; columns: columns"></tr>
    </table>
  `,
})
export class TaskTable {
  rows = input.required<readonly Task[]>();
  protected readonly columns = ['code', 'title'] as const;
}

Логіка тут така: кожна колонка описується окремим ng-container з іменем, а масив columns визначає, які саме й у якому порядку показувати. Тому приховати колонку — це прибрати рядок із масиву, а не чіпати розмітку.

Чому в шаблоні досі *matHeaderCellDef

Тут зірочка — не старий синтаксис *ngIf, а структурна директива самої таблиці: вона позначає шаблон комірки, який Material підставлятиме для кожного рядка. Новий @if її не замінює — це різні речі.

Сортування

<table mat-table [dataSource]="rows()" matSort (matSortChange)="onSort($event)">

  <ng-container matColumnDef="dueDate">
    <th mat-header-cell *matHeaderCellDef mat-sort-header>Термін</th>
    <td mat-cell *matCellDef="let row">{{ row.dueDate | date:'dd.MM' }}</td>
  </ng-container>
onSort(sort: Sort): void {
  this.store.setSort(sort.active, sort.direction);   // напрямок '' означає «скинути»
}

Важлива деталь: MatSort вміє лише одну колонку. Багатоколонкове сортування, якого просить лід, доведеться робити самому — про це урок 5. Material тут дасть тільки стрілки в шапці.

Пагінація

<mat-paginator
  [length]="total()"
  [pageSize]="state().size"
  [pageIndex]="state().page - 1"
  [pageSizeOptions]="[10, 25, 50, 100]"
  (page)="onPage($event)" />
onPage(e: PageEvent): void {
  this.store.setPage(e.pageIndex + 1, e.pageSize);
}

MatPaginator рахує сторінки з нуля, а в людських інтерфейсах прийнято з одиниці. Дрібниця, на якій легко помилитись — тому конвертація робиться в одному місці, на межі компонента.

MatTableDataSource чи власне джерело

MatTableDataSourceМасив або власний DataSource
Сортування й фільтрВбудовані, але однією колонкою й одним рядком-фільтромТвої, будь-якої складності
Джерело данихМасив у памʼятіБудь-який потік
Скільки кодуМало на стартіБільше, але прозоро
Коли братиПростий список без вимогКоли є стор із модуля 4

Для нашого випадку правильна відповідь — передавати вже готовий масив. Стор із модуля 4 вже вміє фільтрувати, сортувати й різати на сторінки; дублювати цю логіку всередині MatTableDataSource означає мати два джерела правди.

// dataSource приймає і масив, і DataSource — тому просто віддаємо готові рядки
<table mat-table [dataSource]="rows()">

Липка шапка й колонка дій

<tr mat-header-row *matHeaderRowDef="columns; sticky: true"></tr>

<ng-container matColumnDef="actions" stickyEnd>
  <th mat-header-cell *matHeaderCellDef></th>
  <td mat-cell *matCellDef="let row">
    <button (click)="edit.emit(row); $event.stopPropagation()">Змінити</button>
  </td>
</ng-container>

Тут спрацьовує знання з модуля 3: липка шапка липне до найближчого предка зі скролом, тож таблиця має лежати в панелі з overflow: auto, а не розсувати сторінку. І stopPropagation на кнопці — щоб клік по дії не відкривав рядок.

trackBy — обовʼязково

<table mat-table [dataSource]="rows()" [trackBy]="trackById">
protected trackById = (_: number, row: Task) => row.id;

Без нього кожне оновлення даних перебудовує всі рядки — з утратою фокуса, скролу й стану вкладених компонентів. Це рівно та сама історія, що з track у @for з модуля 2, тільки для Material-таблиці її задають окремим входом.