Angular Material Table – Стрелки сортировки не отображаются корректно в mat-sort-header

Вопрос или проблема

Я работаю над проектом на Angular, где использую mat-table от Angular Material с включенной сортировкой. Функциональность сортировки работает, но стрелки в заголовках столбцов отображаются некорректно, когда я меняю сортировку с помощью кода TypeScript.

HTML:

<div class="cpage">
  <div class="left-margin"></div>
  <div class="left">
    <form-card header="Левый" [enableBottomPadding]="true">
      <button mat-raised-button (click)="actionB1()">B1</button>
    </form-card>
  </div>
  <div class="gap-between"></div>
  <div class="right">
    <form-card header="ТаблицаДанных" [enableBottomPadding]="true">
      <div style="width: 100%; max-height:90vh; overflow-x:auto">
        <mat-table [dataSource]="dataSource" matSort #sort1="matSort">
          <ng-container matColumnDef="position">
            <mat-header-cell mat-sort-header *matHeaderCellDef> №</mat-header-cell>
            <mat-cell *matCellDef="let element"> {{ element.position }}</mat-cell>
          </ng-container>

          <ng-container matColumnDef="name">
            <mat-header-cell mat-sort-header *matHeaderCellDef> Имя</mat-header-cell>
            <mat-cell *matCellDef="let element"> {{ element.name }}</mat-cell>
          </ng-container>

          <ng-container matColumnDef="weight">
            <mat-header-cell mat-sort-header *matHeaderCellDef> Вес</mat-header-cell>
            <mat-cell *matCellDef="let element"> {{ element.weight }}</mat-cell>
          </ng-container>

          <!-- дополнительные колонки веса -->
          <ng-container matColumnDef="weight1">
            <mat-header-cell *matHeaderCellDef> Вес</mat-header-cell>
            <mat-cell *matCellDef="let element"> {{ element.weight }}</mat-cell>
          </ng-container>

          <ng-container matColumnDef="symbol">
            <mat-header-cell *matHeaderCellDef> Символ</mat-header-cell>
            <mat-cell *matCellDef="let element"> {{ element.symbol }}</mat-cell>
          </ng-container>

          <mat-header-row *matHeaderRowDef="displayedColumns"></mat-header-row>
          <mat-row *matRowDef="let row; columns: displayedColumns;"></mat-row>
        </mat-table>
      </div>
    </form-card>
  </div>
  <div class="right-margin"></div>
</div>

TS:

import { AfterViewInit, ChangeDetectorRef, Component, ViewChild } from '@angular/core';
import { MatSort } from '@angular/material/sort';
import { MatTableDataSource } from "@angular/material/table";

export interface PeriodicElement {
  name: string;
  position: number;
  weight: number;
  symbol: string;
}

const ELEMENT_DATA: PeriodicElement[] = [
  { position: 1, name: 'Водород', weight: 1.0079, symbol: 'H' },
  { position: 2, name: 'Гелий', weight: 4.0026, symbol: 'He' },
  { position: 3, name: 'Литий', weight: 6.941, symbol: 'Li' },
  { position: 4, name: 'Бериллий', weight: 9.0122, symbol: 'Be' },
  { position: 5, name: 'Бор', weight: 10.811, symbol: 'B' },
  { position: 6, name: 'Углерод', weight: 12.0107, symbol: 'C' },
  { position: 7, name: 'Азот', weight: 14.0067, symbol: 'N' },
  { position: 8, name: 'Кислород', weight: 15.9994, symbol: 'O' },
  { position: 9, name: 'Фтор', weight: 18.9984, symbol: 'F' },
  { position: 10, name: 'Неон', weight: 20.1797, symbol: 'Ne' },
];

@Component({
  selector: 'app-debug-table',
  templateUrl: './debug-table.component.html',
  styleUrls: ['./debug-table.component.scss']
})
export class DebugTableComponent implements AfterViewInit {
  displayedColumns: string[] = ['position', 'name', 'weight', 'weight1', 'symbol'];
  dataSource = new MatTableDataSource(ELEMENT_DATA);
  @ViewChild('sort1', { static: false }) sort1: MatSort;

  constructor(private cdr: ChangeDetectorRef) {}

  ngAfterViewInit() {
    this.dataSource.sort = this.sort1;
  }

  actionB1() {
    if (this.sort1.active == 'name') {
      this.sort1.active="weight";
    } else {
      this.sort1.active="name";
    }
    this.dataSource.sort = this.sort1;
    this.cdr.detectChanges();
  }
}

Проблема: Сортировка работает, но стрелки сортировки рядом с заголовками столбцов обновляются некорректно. Нажатие кнопки “B1” меняет сортировку между “name” и “weight”, но стрелки на заголовках mat-sort-header, похоже, не следуют этой смене.

Что я попробовал:

Проверил, что модуль сортировки Angular Material импортирован.
Убедился, что matSort и mat-sort-header правильно добавлены к mat-table и заголовкам ячеек.
Вызвал detectChanges после обновления сортировки, чтобы обновить представление.
Кто-нибудь сталкивался с этой проблемой раньше? Есть идеи, как сделать так, чтобы стрелки отображались правильно?

Angular: 16
Angular Material: 16

Вы можете использовать метод sort из dataSource.sort, который принимает интерфейс ввода MatSortable.

export declare interface MatSortable {
    /** Идентификатор сортируемого столбца. */
    id: string;
    /** Начальное направление сортировки. */
    start: SortDirection;
    /** Отключить очистку состояния сортировки. */
    disableClear: boolean;
}

export declare type SortDirection = 'asc' | 'desc' | '';

Тогда мы можем использовать метод таким образом.

actionB1() {
  let active = null;
  if (this.sort1.active == 'name') {
    active="weight";
  } else {
    active="name";
  }
  this.dataSource.sort!.sort({
    id: active,
    start: 'asc',
    disableClear: true,
  });
  // this.cdr.detectChanges();
}

Полный код:

HTML:

<button mat-raised-button (click)="actionB1()">B1</button>
<table
  mat-table
  matSort
  [dataSource]="dataSource"
  class="mat-elevation-z8"
  (matSortChange)="sortData($event)"
>
  <!--- Обратите внимание, что эти столбцы могут быть определены в любом порядке.
        Фактически отображаемые столбцы задаются как свойство в определении строки" -->

  <!-- Столбец Позиция -->
  <ng-container matColumnDef="position">
    <th mat-header-cell *matHeaderCellDef mat-sort-header="position">№</th>
    <td mat-cell *matCellDef="let element">{{element.position}}</td>
  </ng-container>

  <!-- Столбец Имя -->
  <ng-container matColumnDef="name">
    <th mat-header-cell *matHeaderCellDef mat-sort-header="name">Имя</th>
    <td mat-cell *matCellDef="let element">{{element.name}}</td>
  </ng-container>

  <!-- Столбец Вес -->
  <ng-container matColumnDef="weight">
    <th mat-header-cell *matHeaderCellDef mat-sort-header="weight">Вес</th>
    <td mat-cell *matCellDef="let element">{{element.weight}}</td>
  </ng-container>

  <!-- Столбец Символ -->
  <ng-container matColumnDef="symbol">
    <th mat-header-cell *matHeaderCellDef mat-sort-header="symbol">Символ</th>
    <td mat-cell *matCellDef="let element">{{element.symbol}}</td>
  </ng-container>

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

TS:

import { Component, ViewChild, ChangeDetectorRef, inject } from '@angular/core';
import { MatTableDataSource, MatTableModule } from '@angular/material/table';
import { MatSort } from '@angular/material/sort';
import { MatSortModule, Sort } from '@angular/material/sort';

export interface PeriodicElement {
  name: string;
  position: number;
  weight: number;
  symbol: string;
}

const ELEMENT_DATA: PeriodicElement[] = [
  { position: 1, name: 'Водород', weight: 1.0079, symbol: 'H' },
  { position: 2, name: 'Гелий', weight: 4.0026, symbol: 'He' },
  { position: 3, name: 'Литий', weight: 6.941, symbol: 'Li' },
  { position: 4, name: 'Бериллий', weight: 9.0122, symbol: 'Be' },
  { position: 5, name: 'Бор', weight: 10.811, symbol: 'B' },
  { position: 6, name: 'Углерод', weight: 12.0107, symbol: 'C' },
  { position: 7, name: 'Азот', weight: 14.0067, symbol: 'N' },
  { position: 8, name: 'Кислород', weight: 15.9994, symbol: 'O' },
  { position: 9, name: 'Фтор', weight: 18.9984, symbol: 'F' },
  { position: 10, name: 'Неон', weight: 20.1797, symbol: 'Ne' },
];

function compare(a: number | string, b: number | string, isAsc: boolean) {
  return (a < b ? -1 : 1) * (isAsc ? 1 : -1);
}

/**
 * @title Базовое использование `<table mat-table>`
 */
@Component({
  selector: 'table-basic-example',
  styleUrl: 'table-basic-example.css',
  templateUrl: 'table-basic-example.html',
  standalone: true,
  imports: [MatTableModule, MatSortModule],
})
export class TableBasicExample {
  displayedColumns: string[] = ['position', 'name', 'weight', 'symbol'];
  dataSource = new MatTableDataSource(ELEMENT_DATA);
  cdr = inject(ChangeDetectorRef);
  @ViewChild(MatSort, { static: false }) sort1: MatSort;

  ngAfterViewInit() {
    this.dataSource.sort = this.sort1;
  }

  actionB1() {
    let active = null;
    if (this.sort1.active == 'name') {
      active="weight";
    } else {
      active="name";
    }
    this.dataSource.sort!.sort({
      id: active,
      start: 'asc',
      disableClear: true,
    });
    // this.cdr.detectChanges();
  }

  sortData(sort: Sort) {
    const data = this.dataSource.data.slice();
    if (!sort.active || sort.direction === '') {
      this.dataSource.data = data;
      return;
    }

    this.dataSource.data = data.sort((a, b) => {
      const isAsc = sort.direction === 'asc';
      switch (sort.active) {
        case 'name':
          return compare(a.name, b.name, isAsc);
        case 'weight':
          return compare(a.weight, b.weight, isAsc);
        case 'symbol':
          return compare(a.symbol, b.symbol, isAsc);
        default:
          return 0;
      }
    });
  }
}

Демо Stackblitz

Ответ или решение

Проблема: неправильное отображение стрелок сортировки в Angular Material Table

В процессе работы над проектом на Angular с использованием mat-table из Angular Material, возникает ситуация, когда стрелки сортировки в заголовках столбцов отображаются некорректно. В вашем случае функциональность сортировки работает, однако при переключении сортировки с помощью кнопки "B1", стрелки не обновляются как ожидалось. Давайте подробно рассмотрим, как это исправить.

1. Обновление кода

HTML Разметка

Ваш оригинальный код HTML выглядит достаточно правильно, с корректным использованием mat-sort-header. Но давайте убедимся, что мы правильно определили заголовки с сортировкой:

<mat-header-cell mat-sort-header="name" *matHeaderCellDef> Name </mat-header-cell>
<mat-header-cell mat-sort-header="weight" *matHeaderCellDef> Weight </mat-header-cell>

Вы сделали это верно, но возможно нужно проверить, что все заголовки столбцов, которые должны сортироваться, правильно включают атрибут mat-sort-header.

Проблема в TypeScript коде

В вашем методе actionB1, в случае переключения сортировки, вы используете код:

this.sort1.active = "weight"; // или "name"
this.dataSource.sort = this.sort1;
this.cdr.detectChanges(); // Необходимо ли?

Ваш код присваивает новое значение active свойству sort1, но при этом в методе actionB1 не была правильно обновлена сортировка самой таблицы. Рекомендуется использовать метод sort объекта dataSource.sort, который принимает объект типа MatSortable.

2. Исправленный вариант кода

Обновим метод actionB1 следующим образом:

actionB1() {
  let active = this.sort1.active === 'name' ? 'weight' : 'name';
  this.dataSource.sort.sort({
    id: active,
    start: 'asc',  // предполагаем, что сортировка всегда будет восходящей
    disableClear: false, // можно оставить false, если хотите, чтобы сортировка не сбрасывалась
  });
}

3. Проверка импорта модулей

Убедитесь, что в вашем модуле импортированы все необходимые модули Angular Material. Проверьте, что в вашем основном модуле (например, AppModule) правильно импортирован MatTableModule и MatSortModule.

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

@NgModule({
  imports: [
    MatTableModule,
    MatSortModule,
    // другие нужные модули
  ],
  declarations: [
    // ваши компоненты
  ],
})
export class AppModule { }

4. Окончательные замечания

Подводя итог, убедитесь, что вы используете правильные методы для обновления сортировки в таблице. Исправленный код должен правильно обрабатывать отображение стрелок. Также полезно проверить в браузере инструменты разработчика на наличие ошибок или предупреждений, которые могут указать на проблемы с рендерингом.

Обратите внимание, что использование detectChanges() может быть необходимо в некоторых случаях, но в данном контексте его использование может быть лишним, так как Angular должен самостоятельно отслеживать изменения в компоненте.

Если после внесения этих изменений проблема останется, стоит дополнительно проверить стили и убедиться, что конфликтов со сторонними библиотеками или CSS не возникает.

Оцените материал
Добавить комментарий

Капча загружается...