A demonstração de classificação da esteira não funciona


107

Estou tentando fazer a mat-tableclassificação funcionar localmente e, embora possa fazer com que os dados sejam exibidos conforme o esperado, clicar na linha do cabeçalho não faz a classificação como nos exemplos online (nada acontece). Estou tentando fazer com que esta demonstração funcione localmente: https://material.angular.io/components/sort/overview https://plnkr.co/edit/XF5VxOSEBxMTd9Yb3ZLA?p=preview

Eu gerei um novo projeto com Angular CLI, então segui estas etapas: https://material.angular.io/guide/getting-started

Aqui estão meus arquivos locais:

app.module.ts

import { BrowserModule } from '@angular/platform-browser';
import { NgModule } from '@angular/core';
import { MatSort, MatTableModule } from '@angular/material';

import { AppComponent } from './app.component';
import { TableSortingExample } from './table-sorting-example';

@NgModule({
  declarations: [
    AppComponent,
    TableSortingExample,
    MatSort
  ],
  imports: [
    BrowserModule,
    MatTableModule
  ],
  providers: [],
  bootstrap: [AppComponent]
})
export class AppModule { }

app.component.ts

import { Component } from '@angular/core';

@Component({
  selector: 'app-root',
  templateUrl: './app.component.html',
  styleUrls: ['./app.component.css']
})
export class AppComponent {
  title = 'app';
}

app.component.html

<div style="text-align:center">
  <h1>
    Welcome to {{title}}!
  </h1>
  <table-sorting-example></table-sorting-example>
</div>

table-sorting-example.html

<div class="example-container mat-elevation-z8">
  <mat-table #table [dataSource]="dataSource" matSort>

    <!--- Note that these columns can be defined in any order.
          The actual rendered columns are set as a property on the row definition" -->

    <!-- ID Column -->
    <ng-container matColumnDef="userId">
      <mat-header-cell *matHeaderCellDef mat-sort-header> ID </mat-header-cell>
      <mat-cell *matCellDef="let row"> {{row.id}} </mat-cell>
    </ng-container>

    <!-- Progress Column -->
    <ng-container matColumnDef="progress">
      <mat-header-cell *matHeaderCellDef mat-sort-header> Progress </mat-header-cell>
      <mat-cell *matCellDef="let row"> {{row.progress}}% </mat-cell>
    </ng-container>

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

    <!-- Color Column -->
    <ng-container matColumnDef="color">
      <mat-header-cell *matHeaderCellDef mat-sort-header> Color </mat-header-cell>
      <mat-cell *matCellDef="let row" [style.color]="row.color"> {{row.color}} </mat-cell>
    </ng-container>

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


<!-- Copyright 2017 Google Inc. All Rights Reserved.
    Use of this source code is governed by an MIT-style license that
    can be found in the LICENSE file at http://angular.io/license -->

table-sorting-example.ts

import {Component, ViewChild} from '@angular/core';
import {DataSource} from '@angular/cdk/collections';
import {MatSort} from '@angular/material';
import {BehaviorSubject} from 'rxjs/BehaviorSubject';
import {Observable} from 'rxjs/Observable';
import 'rxjs/add/operator/startWith';
import 'rxjs/add/observable/merge';
import 'rxjs/add/operator/map';

/**
 * @title Table with sorting
 */
@Component({
  selector: 'table-sorting-example',
  styleUrls: ['table-sorting-example.css'],
  templateUrl: 'table-sorting-example.html',
})
export class TableSortingExample {
  displayedColumns = ['userId', 'userName', 'progress', 'color'];
  exampleDatabase = new ExampleDatabase();
  dataSource: ExampleDataSource | null;

  @ViewChild(MatSort) sort: MatSort;

  ngOnInit() {
    this.dataSource = new ExampleDataSource(this.exampleDatabase, this.sort);
  }
}

/** Constants used to fill up our data base. */
const COLORS = ['maroon', 'red', 'orange', 'yellow', 'olive', 'green', 'purple',
  'fuchsia', 'lime', 'teal', 'aqua', 'blue', 'navy', 'black', 'gray'];
const NAMES = ['Maia', 'Asher', 'Olivia', 'Atticus', 'Amelia', 'Jack',
  'Charlotte', 'Theodore', 'Isla', 'Oliver', 'Isabella', 'Jasper',
  'Cora', 'Levi', 'Violet', 'Arthur', 'Mia', 'Thomas', 'Elizabeth'];

export interface UserData {
  id: string;
  name: string;
  progress: string;
  color: string;
}

/** An example database that the data source uses to retrieve data for the table. */
export class ExampleDatabase {
  /** Stream that emits whenever the data has been modified. */
  dataChange: BehaviorSubject<UserData[]> = new BehaviorSubject<UserData[]>([]);
  get data(): UserData[] { return this.dataChange.value; }

  constructor() {
    // Fill up the database with 100 users.
    for (let i = 0; i < 100; i++) { this.addUser(); }
  }

  /** Adds a new user to the database. */
  addUser() {
    const copiedData = this.data.slice();
    copiedData.push(this.createNewUser());
    this.dataChange.next(copiedData);
  }

  /** Builds and returns a new User. */
  private createNewUser() {
    const name =
      NAMES[Math.round(Math.random() * (NAMES.length - 1))] + ' ' +
      NAMES[Math.round(Math.random() * (NAMES.length - 1))].charAt(0) + '.';

    return {
      id: (this.data.length + 1).toString(),
      name: name,
      progress: Math.round(Math.random() * 100).toString(),
      color: COLORS[Math.round(Math.random() * (COLORS.length - 1))]
    };
  }
}

/**
 * Data source to provide what data should be rendered in the table. Note that the data source
 * can retrieve its data in any way. In this case, the data source is provided a reference
 * to a common data base, ExampleDatabase. It is not the data source's responsibility to manage
 * the underlying data. Instead, it only needs to take the data and send the table exactly what
 * should be rendered.
 */
export class ExampleDataSource extends DataSource<any> {
  constructor(private _exampleDatabase: ExampleDatabase, private _sort: MatSort) {
    super();
  }

  /** Connect function called by the table to retrieve one stream containing the data to render. */
  connect(): Observable<UserData[]> {
    const displayDataChanges = [
      this._exampleDatabase.dataChange,
      this._sort.sortChange,
    ];

    return Observable.merge(...displayDataChanges).map(() => {
      return this.getSortedData();
    });
  }

  disconnect() {}

  /** Returns a sorted copy of the database data. */
  getSortedData(): UserData[] {
    const data = this._exampleDatabase.data.slice();
    if (!this._sort.active || this._sort.direction == '') { return data; }

    return data.sort((a, b) => {
      let propertyA: number|string = '';
      let propertyB: number|string = '';

      switch (this._sort.active) {
        case 'userId': [propertyA, propertyB] = [a.id, b.id]; break;
        case 'userName': [propertyA, propertyB] = [a.name, b.name]; break;
        case 'progress': [propertyA, propertyB] = [a.progress, b.progress]; break;
        case 'color': [propertyA, propertyB] = [a.color, b.color]; break;
      }

      let valueA = isNaN(+propertyA) ? propertyA : +propertyA;
      let valueB = isNaN(+propertyB) ? propertyB : +propertyB;

      return (valueA < valueB ? -1 : 1) * (this._sort.direction == 'asc' ? 1 : -1);
    });
  }
}


/**  Copyright 2017 Google Inc. All Rights Reserved.
 Use of this source code is governed by an MIT-style license that
 can be found in the LICENSE file at http://angular.io/license */

Alguém tem uma ideia de por que ele apareceria como a mesa online, mas não tem a funcionalidade de classificação?


Gostaria de depurar o aplicativo primeiro. Algum erro? execute o ng test --sm=falsee veja o que está saindo.
k.vincent

Está funcionando para mim sem @ViewChild (MatSort) sort: MatSort; Qualquer razão ?
user123456

Respostas:


198

Para qualquer outra pessoa que possa ter esse problema: O problema é que eu não li a referência da API corretamente no site de materiais do angular, a parte que dizia que eu precisava importar MatSortModule. Depois de alterar minha lista de importações em app.module.ts para

imports: [
    BrowserModule,
    MatTableModule,
    MatSortModule
  ],

funcionou bem


45
não há menção a este módulo na documentação. material.angular.io/components/table/overview#sorting eu perdi uma hora com isso também.
Sonic Soul

8
não há problema, pois o texto do cabeçalho pode ser clicado e o ícone também está lá, mas a classificação não está funcionando.
SPnL

2
Verifique se BrowserAnimationsModuletambém é importado em app.module.ts
Augustas

2
Posso dizer que eles são filhos da mãe? Passei 1 hora tentando descobrir por que meu ViewChild não estava funcionando. Eles não podem importar / exportar este MatSortModule do MatTableModule ??
Sampgun de

7
Importei o MatSortModulee BrowserAnimationsModulee assegurei-me de que o valor matColumnDef corresponde ao nome da propriedade, mas ainda não consigo fazer nada com ele.
Trevor

133

Eu tive um problema que a função de classificação estava funcionando, mas não estava classificando corretamente. Percebi que matColumnDeftem que ter o mesmo nome da propriedade minha à class / interfacequal estou me referindo matCellDef.

De acordo com a documentação do Material Angular :

Por padrão, o MatTableDataSource classifica com a suposição de que o nome da coluna classificada corresponde ao nome da propriedade de dados que a coluna exibe.

Por exemplo:

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

O namena matColumnDefdiretiva deve ser o mesmo que o nameusado no <mat-cell>componente.


1
O que em seu exemplo você está se referindo? Seria útil ver sua interface também, para comparação.
isherwood

1
Eu estava usando "Id" como nome da coluna, enquanto a entidade estava tendo "id". A diferença de case foi fazer com que ele não rodasse (devido a uma falha de refatoração) .. Agora está resolvido. Obrigado
NitinSingh

2
Obrigado, é muito útil.
Bohao LI

2
@NitinSingh, e se você precisar chamar uma função no element, como este `{{row.getName ()}}`
codentário

1
Eu te devo uma cerveja porque estou preso neste problema há um tempo e este comentário resolveu o meu problema.
noel

99

Se a tabela estiver dentro de * ngIf, não funcionará. Funcionará se for alterado para [oculto]


33
!!! VOCÊ SALVA MEU DIA !!! Use em vez de <div *ngIf="xxx"> a<div [hidden]="!xxx">
Mark

1
Posso confirmar, isso funcionou para mim também. Obrigado zerg!
clo5ure

1
Muito obrigado, isso me custou muito tempo !!
themightylc

1
Ou apenas defina a fonte de dados em ngAfterViewInit em vez de ngOnInit
user3666653

1
Este é o problema mais "oculto" que poderia acontecer, obrigado pela solução! documentações poderiam ter alertado sobre isso
Raycherr

35

O nome matColumnDef e * o nome do valor real matCellDef devem ser iguais

Exemplo:

<ng-container matColumnDef="oppNo">
    <th mat-header-cell *matHeaderCellDef mat-sort-header>Opportunity Number</th>
    <td mat-cell *matCellDef="let element">{{element.oppNo}}</td>
</ng-container>

No meu caso, oppNo é o mesmo para o nome matColumnDef e o nome * matCellDef e a classificação está funcionando bem.


Interessante. Esse foi o meu caso também. Mas, você sabe o verdadeiro raciocínio por trás disso ou isso é realmente algum tipo de "bug"?
ReturnTable de

22

Adicionar classificação dentro do bloco de tempo limite funciona para mim,

dataSource = new MatTableDataSource(this.articleService.getAllArticles());
setTimeout(() => {
  this.tableDataSource.sort = this.sort;
  this.tableDataSource.paginator = this.paginator;
});

Se você não quiser usar ganchos de ciclo de vida.


1
hack idiota, mas funciona, alguma ideia de por que não funciona sem o tempo limite?
Ruben

Passei muito tempo tentando tudo o mais, pensando que estava ficando louco. Funcionou como um encanto!
willpnw

4
Realmente uma maneira ruim de fazer isso. Funciona porque você está deixando passar algum tempo após a inicialização do componente para que a fonte de dados seja construída e, em seguida, você adiciona sort e paginator. O melhor é mover o datSource buidling em ngOnInit e depois mover as atribuições de classificação e paginador em AfterViewInit. É para isso que existem os ganchos do Lifecycle.
Selam Getachew de

20

Eu também acertei esse problema. Já que você precisa esperar que o filho seja definido, você deve implementar e usar AfterViewInit, não onInit.

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

Impressionante ! Obrigado
Shashank Vivek

Estou usando uma tabela com classificação, filtragem e paginação. Você tem alguma ideia de por que apenas a classificação deve ser definida em ngAfterViewInit? O resto estava trabalhando ngOnInit. É só tentar entender, está consertado graças a você
Nicolas M.

14

Passei horas nesse assunto. Depois de ler vários tópicos, aqui estão as etapas que fiz.

  1. Como @avern mencionou , você precisa importar MatSortModule.
  2. Certifique-se de que NÃO está envolvendo a mesa em a *ngIf. Alterá-lo para [hidden]como @zerg recomendado . (Não entendo porque)

Espero que isto ajude.


Perdi meu dia para descobrir o problema e estúpido não mostra nenhum erro.
surekha shelake

11

Minha solução foi consertar várias coisas (basicamente mesclando a maioria das soluções nesta página).

Coisas a verificar:

  1. BrowserModule, MatTableModule, MatSortModule Os módulos devem ser importados no arquivo de módulos raiz.
  2. Certifique-se de ter usado a MatTableDatasourceclasse e passar seu array de dados como um parâmetro
  3. Certifique-se de que sua tabela não esteja aninhada em uma *ngIf=....diretiva. Use outras operações condicionais (ainda não entendo por quê).

3

Para mim, substituir * ngIf pelo atributo [oculto] para a tag mat-table funcionou. Como postar isso como um bug para a comunidade Angular Material?


3

Corrigi isso em meu cenário nomeando os dados da tabela com o mesmo nome de * matColumnDef. Por exemplo:

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

Em vez de

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

3

Houve 2 problemas para mim.

  1. Os nomes matColumnDef e matCellDef -> são diferentes
  2. Estava recebendo os dados do serviço. A classificação ngOnInit não estava funcionando. Substituído por

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


2

Encontrei este blog antigo que me ajudou a fazê-lo funcionar: https://www.jeffryhouser.com/index.cfm/2018/10/23/Five-Reasons-My-ngMaterial-Table-wont-sort

  1. Certifique-se de importar MatSortModule
  2. Especifique o matSortcabeçalho
  3. Certifique-se de envolver sua fonte de dados em um MatTableDataSource
    • Este é o que me ajudou a resolver (entendeu? Resolver ). No modelo, eu estava me referindo ao array diretamente ( <table mat-table [dataSource]="this.products" matSort>), mas deveria ter usado o objeto de fonte de dados que inicializei no código ( <table mat-table [dataSource]="this.dataSource" matSort>). A fonte de dados é inicializada comodataSource = new MatTableDataSource(this.products)
  4. Informe a fonte de dados sobre sua classificação, em ngOnInit/ngAfterViewInit
  5. Escreva seu próprio tipo, se você não quiser usar MatTableDataSource

1

Se sua tabela estiver dentro de um * ngIf e você achar que isso tem algo a ver com o fato de não classificar sua mesa, então especificar sua própria sortingDataAccessorfunção pode resolver o problema como fez para mim. Eu tenho minha tabela dentro de alguns * ngIfs e retirá-la desses * ngIfs não fazia sentido:

`ngAfterViewInit(): void {
        this.matchesDataSource.sort = this.sort;
        this.matchesDataSource.sortingDataAccessor = previewMatchSortingFn;
    }`

`export function previewMatchSortingFn(item: Match, header: string): string | number {
    switch (header) {
        case 'home':
            return item.homeTeam.name;
        case 'away':
            return item.awayTeam.name;
        case 'date':
            if (item.dateTime) {
                // this will return the number representation of the date
                return item.dateTime.valueOf();
            }
            return;
        default:
            break;
    }
}`

1

Uma das razões pelas quais MatSort pode não funcionar é quando ele é adicionado a um dataSource (ou seja, this.dataSource.sort = this.sort) antes de ser definido. Pode haver vários motivos para isso:

  1. se você adicionar a classificação em ngOnInit. Neste ponto, o modelo ainda não foi renderizado, então o MatSort que você obteve @ViewChild(MatSort, { static: true }) sort: MatSort;é indefinido e, compreensivelmente, não fará nada. Uma solução para esse problema é mudar this.dataSource.sort = sortpara ngAfterViewInit. Quando ngAfterViewInit é chamado, seu componente é renderizado e MatSort deve ser definido.

  2. quando você usa * ngIf é seu modelo em seu elemento de tabela ou um se seus elementos pais e este * ngIf faz com que sua tabela não seja renderizada no momento em que você tenta definir o MatSort. Por exemplo, se você tiver *ngIf="dataSource.data.length > 0"em seu elemento de tabela (para renderizá-lo apenas se houver dados presentes) e você definir this.dataSource.sort = this.sortlogo após definir this.dataSource.datacom seus dados. A visualização do componente ainda não será renderizada, então MatSort ainda ficará indefinido.

Para que o MatSort funcione e ainda mostre condicionalmente a sua tabela, você pode decidir substituir o *ngIfpor [hidden]conforme indicado em várias outras respostas. No entanto, se você deseja manter sua instrução * ngIf, pode usar a solução a seguir. Esta solução funciona para Angular 9, eu não testei em versões anteriores, então não tenho certeza se funciona lá.

Encontrei esta solução aqui: https://github.com/angular/components/issues/10205

Em vez de colocar:

@ViewChild(MatSort) sort: MatSort;

use um setter para matSort. Este setter irá disparar assim que matSort em sua visão mudar (ou seja, for definido pela primeira vez), ele não irá disparar quando você mudar sua classificação clicando nas setas. Será parecido com este:

@ViewChild(MatSort) set matSort(sort: MatSort) {
    this.dataSource.sort = sort;
}

Se você tiver outras funções que (programaticamente) alteram a classificação, não tenho certeza se ele vai disparar novamente, eu não testei isso. Se você não quiser ter certeza de que só definirá a classificação se a classificação for indefinida, você pode fazer algo assim:

@ViewChild(MatSort) set matSort(sort: MatSort) {
    if (!this.dataSource.sort) {
        this.dataSource.sort = sort;
    }
}

0

Veja se você tem algum erro de javascript no console. Pode ser que alguma outra coisa falhou antes da inicialização da classificação.

Ao utilizar nosso site, você reconhece que leu e compreendeu nossa Política de Cookies e nossa Política de Privacidade.
Licensed under cc by-sa 3.0 with attribution required.