3

I have multiple dynamically created Angular tables, each displaying the same columns but with different data. I need to sort the columns in each table separately. I currently have two tables. When I click on the column header arrow on the first one, it sorts correctly. Clicking on the same column in the second table does nothing.

Here is the relevant html:

  <div appMaterialElevation *ngFor="let item of tables; let i = index">  
    <table
      mat-table
      [dataSource]="item.dataSource"
      multiTemplateDataRows
      matSort
      matSortActive="configName"
      matSortDirection="asc"
    > 
      <ng-container matColumnDef="configName">
        <th mat-header-cell *matHeaderCellDef mat-sort-header>Table Name</th>
      </ng-container>

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

Here is the relevant TS:

import { Component, ViewChild, ViewChildren, QueryList, OnInit, AfterViewInit, OnDestroy } from '@angular/core';
export class Row {
  configName: string;
}

export class FormatData {
  formatName: string;
  dataSource: MatTableDataSource<Row>;
  selection: SelectionModel<Row>;
}

export class ConfigureFormatsComponent implements OnInit, AfterViewInit, OnDestroy {
  @ViewChildren(MatPaginator) paginators = new QueryList<MatPaginator>();
  @ViewChildren(MatSort) sorts = new QueryList<MatSort>();
  tables: FormatData[];
  displayedColumns: string[] = ['configName'];

  getconfigformats() {
    this.tables = [] as FormatData[];    
    this.myService.getMyData()
      .subscribe((configList: MyConfigs[]) => {
        let table = new FormatData();
        let configNamesList = [] as Row[];
        configList.forEach(config => {
          let row = new Row();
          row.configName = config.configName;
          configNamesList.push(row);
         });
       table.dataSource = new MatTableDataSource<Row>(configNamesList);
       table.selection = new SelectionModel<Row>(true, []);
       this.tables.push(table);
       this.ngAfterViewInit();
     }
   });
  }

  ngAfterViewInit() {
    for (let i=0; i<this.tables.length; i++) {
      let table = this.tables[i];
      table.dataSource.sort = this.sorts.toArray()[i];
      table.dataSource.paginator = this.paginators.toArray()[i];
    };
  }

Can anybody see my mistake?

2
  • Maybe try creating a child component for the table and pass in the datasource? You would then ensure there is one and only one MatSort per table and not need to loop through a QueryList Commented Mar 4, 2022 at 16:47
  • you would be best of seperating the table into its own component and have an @input on it to set the datasource, that way you encapsulate table functionality Commented Mar 8, 2022 at 13:08

1 Answer 1

3
+50

You need assing the sort to each "dataSource"

So,I imagine you want to make some like

this.tables.forEach((item,i)=>{
  item.dataSource.sort=this.sort.find((_,index)=>index==i)
}

See how we select the QueryList using find((_x,index)

IMPORTANT NOTE: is

   //see that is without ()
  @ViewChildren(MatPaginator) paginators = new QueryList<MatPaginator>;
  @ViewChildren(MatSort) sorts = new QueryList<MatSort>;

Update it's necesary "give time to Angular" to draw the tables. we can make enclosing into a setTimeout when assign the matsort.

In the stackblitz I use some like

  dataSources:MatTableDataSource<any>[]=[]
  @ViewChildren(MatSort) sorts:QueryList<MatSort>

  ngOnInit()
  {
    service.getData().subscribe((res:any[])=>{
      res.forEach((x,index)=>{
           this.dataSources[index]=new MatTableDataSource(x)
       })
       setTimeout(()=>{
        this.dataSources.forEach((x,index)=>{
          x.sort=this.sorts.find((_,i)=>i==index)
        })
       })
      })
  }

Update2 I don't know about your code, so you can create a function

setSorts()
{
   setTimeout(()=>{
      this.tables.forEach((item:any,index:number)=>{
        const sort=this.sorts.find((_,i:number)=>i==index)
        item.dataSource.sort=sort
      })
   })
}

NOTE: important your dataSource should be a MatTableDataSource, that's. If you has an array you should write

table.dataSource=new MatTableDataSource<Row>(yourArray);

It's necesary the setTimeout because, when you add an element to your array still you have no sort. You need "wait" Angular paint the tables. the setTimeout say to Angular: "eh! paint the tables and, after you've painted execute the functions under the setTimeout.

And call always you add/remove one element to the array "this.tables" (each time you make a this.tables.push(..) or this.tables.remove)

About the use of find((_x,index)=>....), an array or a queryList has methods: forEach,map,find.. that generally we use with an unique "argument", e.g. `

myArray.forEach((x:any)=>{...x is the value of the element..}

But all this methods allow a second argument that is the index of the element, e.g.

myArray.forEach((x:any,index:number)=>{
 ...x is the value of the element and index the position..
 console.log(x,index)
}

When you use with "find" you can get the element in the position "index", as we needn't the value of the variable, I usually use a _ (but you can use any name of the variable

Sign up to request clarification or add additional context in comments.

9 Comments

new always needs (). Anyway this didn't work either. Got error about ngOnDestroy is missing in type 'ConfigurationFormatsComponent' but required in type 'OnDestroy' even though it is defined: ngOnDestory(){}
sorry, I don't see that you're trying create a QueryList. Really should be @ViewChildren(MatPaginator) paginators!:QueryList<MatPaginator>; -the "!" is to say Angular that you yet know that can be undefined" (If I have time try to create a stackblitz
@user3217883, I update the answer adding a stackblitz
Thanks for the stackblitz example. That is great! I'm trying to integrate your example into my code. In my code it takes three different database calls to finally get all the data for the tables, which are stored in the "tables" variable.
see that when you after* create the tables, should enclosed under setTimeout() to assign the sort (until tables has no value, Angular not "paint" the tables. so you need give time to paint(*). Really you not give time, the setTimeout say to Angular "paint the tables and when you finished, remember that you should make the instructions under setTimeout"
|

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.