La démo de tri sur table ne fonctionne pas


107

J'essaie de faire fonctionner le mat-tabletri localement, et bien que je puisse faire apparaître les données comme prévu, cliquer sur la ligne d'en-tête ne fait pas le tri comme sur les exemples en ligne (rien ne se passe du tout). J'essaie de faire fonctionner cette démo localement: https://material.angular.io/components/sort/overview https://plnkr.co/edit/XF5VxOSEBxMTd9Yb3ZLA?p=preview

J'ai généré un nouveau projet avec Angular CLI, puis j'ai suivi ces étapes: https://material.angular.io/guide/getting-started

Voici mes fichiers locaux:

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 */

Quelqu'un a-t-il une idée de la raison pour laquelle il apparaîtrait comme le tableau en ligne mais ne dispose pas de la fonctionnalité de tri?


Je déboguerais d'abord l'application. Des erreurs? lancez ng test --sm=falseet voyez ce qui sort.
k.vincent

Cela fonctionne pour moi sans le tri @ViewChild (MatSort): MatSort; N'importe quelle raison ?
user123456

Réponses:


198

Pour tous ceux qui pourraient avoir ce problème: Le problème était que je n'avais pas lu correctement la référence API sur le site Web des matériaux angulaires, la partie qui disait que je devais importer MatSortModule. Après avoir changé ma liste d'importations dans app.module.ts en

imports: [
    BrowserModule,
    MatTableModule,
    MatSortModule
  ],

ça a bien fonctionné


45
il n'y a aucune mention de ce module dans la documentation. material.angular.io/components/table/overview#sorting J'ai également perdu une heure là-dessus.
Sonic Soul

8
c'est bien, dans l'en-tête le texte est cliquable et l'icône est également là mais le tri ne fonctionne toujours pas.
SPnL

2
Vérifiez si BrowserAnimationsModuleest également importé dans app.module.ts
Augustas

2
Puis-je dire que ce sont des SOB? J'ai passé 1 heure à essayer de comprendre pourquoi mon ViewChild ne fonctionnait pas. Ne peuvent-ils pas importer / exporter ce MatSortModule à partir du MatTableModule ??
Sampgun

7
J'ai importé le MatSortModuleet BrowserAnimationsModule, et je me suis assuré que la valeur matColumnDef correspond au nom de la propriété, mais je suis toujours incapable de faire quoi que ce soit.
Trevor

134

J'ai eu un problème car la fonction de tri fonctionnait mais elle ne triait pas correctement. J'ai réalisé que matColumnDefdoit avoir le même nom de la propriété de mon class / interfaceauquel je fais référencematCellDef .

Selon la documentation du matériau angulaire :

Par défaut, MatTableDataSource trie en supposant que le nom de la colonne triée correspond au nom de propriété de données que la colonne affiche.

Par exemple:

<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>

Le namedans la matColumnDefdirective doit être le même que celui nameutilisé dans le <mat-cell>composant.


1
À quoi faites-vous référence dans votre exemple? Il serait également utile de voir votre interface, à des fins de comparaison.
isherwood

1
J'utilisais "Id" comme nom de colonne alors que l'entité avait "id". La différence de cas faisait qu'il ne fonctionnait pas (en raison d'un échec de refactoring). Maintenant, c'est résolu. Merci
NitinSingh

2
Merci, c'est très utile.
Bohao LI

2
@NitinSingh, que faire si vous avez besoin d'appeler une fonction sur le element, comme celle-ci `{{row.getName ()}}`
codentaire

1
Je vous dois totalement une bière parce que je suis coincé sur ce problème depuis un moment et ce commentaire a résolu mon problème.
noel

99

Si la table est à l'intérieur de * ngIf, cela ne fonctionnera pas. Cela fonctionnera s'il est remplacé par [masqué]


33
!!! VOUS SAUVEZ MA JOURNÉE !!! Utilisez au lieu de <div *ngIf="xxx"> la<div [hidden]="!xxx">
Mark

1
Je peux confirmer, cela a fonctionné pour moi aussi. Merci zerg!
clo5ure

1
Merci beaucoup, cela m'a coûté beaucoup de temps !!
themightylc

1
Ou simplement définir la source de données dans ngAfterViewInit au lieu de ngOnInit
user3666653

1
C'est le problème le plus "caché" qui puisse arriver, merci pour la solution! les documentations auraient pu avertir à ce sujet
Raycherr

35

Le nom matColumnDef et le nom de la valeur réelle * matCellDef doivent être identiques

Exemple:

<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>

Dans mon cas, oppNo est le même pour le nom matColumnDef et le nom * matCellDef et le tri fonctionne bien.


Intéressant. C'était aussi le cas pour moi. Mais, connaissez-vous le raisonnement réel derrière cela ou c'est en fait une sorte de "bug"?
ReturnTable

22

L'ajout de tri dans le bloc de délai d'expiration fonctionne pour moi,

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

Si vous ne souhaitez pas utiliser de hooks de cycle de vie.


1
hack stupide mais ça marche, une idée pourquoi ça ne marche pas sans le délai?
Ruben

J'ai passé beaucoup trop de temps à essayer tout le reste, pensant que je devenais fou. A travaillé comme un charme!
willpnw

4
Vraiment une mauvaise façon de le faire. Cela fonctionne parce que vous laissez passer un certain temps après l'initialisation du composant afin que la source de données soit générée, puis vous ajoutez le tri et le paginateur. Le mieux est de déplacer le bâtiment datSource dans ngOnInit, puis de déplacer les attributions de tri et de pagination dans AfterViewInit. C'est pour cela que les hooks Lifecycle existent.
Selam Getachew

20

J'ai également touché ce problème. Puisque vous devez attendre que l'enfant soit défini, vous devez implémenter et utiliser AfterViewInit, pas onInit.

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

Impressionnant ! Merci
Shashank Vivek

J'utilise une table avec tri, filtrage et pagination. Savez-vous pourquoi seul le tri doit être défini ngAfterViewInit? Le reste travaillait à partir de ngOnInit. C'est juste pour essayer de comprendre, c'est réparé grâce à vous
Nicolas M.

14

J'ai passé des heures sur cette question. Après avoir lu un certain nombre de fils, voici les étapes que j'ai suivies.

  1. Comme @avern l'a mentionné , vous devez importer MatSortModule.
  2. Assurez-vous de NE PAS inclure le tableau dans un fichier *ngIf. Remplacez-le [hidden]par @zerg recommandé . (Je ne comprends pas pourquoi)

J'espère que cela t'aides.


Son a gaspillé ma journée pour découvrir le problème et stupide ne montre aucune erreur.
surekha shelake le

11

Ma solution était de corriger plusieurs choses (en gros en fusionnant la plupart des solutions de cette page).

Choses à vérifier:

  1. BrowserModule, MatTableModule, MatSortModule Les modules doivent être importés dans le fichier des modules racine.
  2. Assurez-vous d'avoir utilisé la MatTableDatasourceclasse et passez votre tableau de données en tant que paramètre
  3. Assurez-vous que votre table n'est pas imbriquée dans une *ngIf=....directive. Utilisez à la place d'autres opérations conditionnelles (vous ne comprenez toujours pas pourquoi).

3

Pour moi, remplacer * ngIf par l'attribut [hidden] pour la balise mat-table a fonctionné. Comment publier celui-ci comme un bogue dans la communauté Angular Material?


3

J'ai corrigé cela dans mon scénario en nommant les données de la table avec le même nom que * matColumnDef Par exemple:

<!-- 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>

Au lieu

<!-- 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

Il y avait 2 problèmes pour moi.

  1. Les noms matColumnDef et matCellDef -> sont différents
  2. J'obtenais les données du service. Le tri ngOnInit ne fonctionnait pas. Remplacé par

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


2

J'ai trouvé cet ancien blog qui m'a aidé à le faire fonctionner: https://www.jeffryhouser.com/index.cfm/2018/10/23/Five-Reasons-My-ngMaterial-Table-wont-sort

  1. Assurez-vous d'importer MatSortModule
  2. Spécifiez l'en- matSorttête
  3. Assurez-vous d'envelopper votre source de données dans un MatTableDataSource
    • C'est celui qui m'a aidé à le trier (l'obtenir? Le trier ). Dans le modèle, je faisais directement référence au tableau ( <table mat-table [dataSource]="this.products" matSort>) mais j'aurais dû utiliser l'objet de source de données que j'ai initialisé dans le code ( <table mat-table [dataSource]="this.dataSource" matSort>). La source de données est initialisée commedataSource = new MatTableDataSource(this.products)
  4. Indiquez à la source de données votre tri, dans ngOnInit/ngAfterViewInit
  5. Écrivez votre propre tri, si vous ne souhaitez pas utiliser MatTableDataSource

1

Si votre table est à l'intérieur d'un * ngIf et que vous pensez que cela a quelque chose à voir avec le fait que vous ne triez pas votre table, alors la spécification de votre propre sortingDataAccessorfonction pourrait résoudre le problème comme cela a été le cas pour moi. J'ai ma table dans quelques * ngIfs et la retirer de ces * ngIfs n'avait pas de sens:

`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

L'une des raisons pour lesquelles MatSort peut ne pas fonctionner est lorsqu'il est ajouté à une source de données (c'est-à-dire this.dataSource.sort = this.sort) avant qu'il ne soit défini. Il peut y avoir plusieurs raisons à cela:

  1. si vous ajoutez le tri dans ngOnInit. À ce stade, le modèle n'est pas encore rendu, donc le MatSort que vous obtenez @ViewChild(MatSort, { static: true }) sort: MatSort;n'est pas défini et ne fera naturellement rien. Une solution à ce problème consiste à passer this.dataSource.sort = sortà ngAfterViewInit. Lorsque ngAfterViewInit est appelé, votre composant est rendu et MatSort doit être défini.

  2. lorsque vous utilisez * ngIf est votre modèle sur votre élément de table ou un s'il s'agit d'éléments parents et que * ngIf empêche le rendu de votre table au moment où vous essayez de définir MatSort. Par exemple, si vous avez *ngIf="dataSource.data.length > 0"sur votre élément de table (pour le rendre uniquement s'il y a des données présentes) et que vous définissez this.dataSource.sort = this.sortjuste après avoir défini this.dataSource.dataavec vos données. La vue du composant ne sera pas encore rendue, donc MatSort sera toujours indéfini.

Afin de faire fonctionner MatSort tout en affichant conditionnellement votre table, vous pouvez décider de remplacer le *ngIfpar [hidden]comme indiqué dans plusieurs autres réponses. Cependant, si vous souhaitez conserver votre instruction * ngIf, vous pouvez utiliser la solution suivante. Cette solution fonctionne pour Angular 9, je ne l'ai pas testée sur les versions précédentes donc je ne suis pas sûr que cela fonctionne là-bas.

J'ai trouvé cette solution ici: https://github.com/angular/components/issues/10205

Au lieu de mettre:

@ViewChild(MatSort) sort: MatSort;

utilisez un setter pour matSort. Ce setter se déclenchera une fois que matSort dans votre vue change (c'est-à-dire qu'il est défini la première fois), il ne se déclenchera pas lorsque vous modifierez votre tri en cliquant sur les flèches. Cela ressemblera à ceci:

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

Si vous avez d'autres fonctions qui modifient (par programme) le tri, je ne suis pas sûr qu'il se déclenchera à nouveau, je n'ai pas testé cela. Si vous ne voulez pas vous assurer qu'il définit le tri uniquement si le tri n'était pas défini, vous pouvez faire quelque chose comme ceci:

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

0

Voyez si vous avez des erreurs javascript dans la console. Il se peut qu'une autre chose ait échoué avant l'initialisation de votre tri.

En utilisant notre site, vous reconnaissez avoir lu et compris notre politique liée aux cookies et notre politique de confidentialité.
Licensed under cc by-sa 3.0 with attribution required.