Home > Mobile >  Customizable Angular material table
Customizable Angular material table

Time:08-09

Im kinda beginner with Angular and just wondering if this is possible to achive.

list-people.component.html

<my-table [dataSource]="dataSource" [pagination]="true">

  <my-column id="id" name="ID" sortable></my-column>

  <my-column id="user" name="Full Name" *row="let row"> //Maybe somehow get the row data
    {{ row.user.name }} {{ row.user.last }}
  </my-column>

  <my-column id="country" name="Country" sortable></my-column>

  <my-column id="actions" name="">
    <button (click)="edit(this)">Edit</button> //should get row data to edit click function
  </my-column>

</my-table>

list-people.component.ts (Short ver)

export class ListPeople {
  data: MatTableDataSource<?>;// ? Should be a interface

  ngOnInit() {
    const list = [
      {
        id: 1,
        country: 'England',
        user: {
          name: 'Peter',
          last: 'Gustavf'
        }
      }
    ];

    this.data = new MatTableDataSource(list);
  }
}

Ive tried but failed, so is this hard or should i give up on this? I did manage to print the values for each my-column inside my-table dont know what to do to continue

CodePudding user response:

Have your tried to check any of the examples on the Angular Material Table site

A basic example applied to your provided data could be like this

<table mat-table [dataSource]="data">
  <ng-container matColumnDef="id">
    <th mat-header-cell *matHeaderCellDef> ID. </th>
    <td mat-cell *matCellDef="let element"> {{element.id}} </td>
  </ng-container>

  <ng-container matColumnDef="country">
    <th mat-header-cell *matHeaderCellDef> Country </th>
    <td mat-cell *matCellDef="let element"> {{element.country}} </td>
  </ng-container>

  <ng-container matColumnDef="user">
    <th mat-header-cell *matHeaderCellDef> User </th>
    <td mat-cell *matCellDef="let element"> {{element.user.name}} {{element.user.last}} </td>
  </ng-container>

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

And in your ListPeople component, add displayedColumns property

displayedColumns: string[] = ['id', 'country', 'user'];

CodePudding user response:

Sure you can.

Imagine a .html to create a mat-data-table

<table *ngIf="displayedColumns" mat-table [dataSource]="dataSource">
  <ng-container
    *ngFor="let cell of cells; let i = index"
    [matColumnDef]="displayedColumns[i]"
  >
    <th mat-header-cell *matHeaderCellDef>{{ titles[i]}}</th>
    <td mat-cell *matCellDef="let element"></td>
  </ng-container>
  <tr mat-header-row *matHeaderRowDef="displayedColumns"></tr>
  <tr mat-row *matRowDef="let row; columns: displayedColumns"></tr>
</table>

You need datasource, displayedColumns, an Array titles and important the "TemplateRef" of the *matCellDef

So the idea is that in your .html you has some like

<app-my-table [source]="items">
  <td app-cell  name="name" title="Name" *row="let data">{{ data.name }}</td>
  <td app-cell  name="id" title="Id" *row="let data">{{ data.id }}</td>
</app-my-table>

See that you need two Directive, the directive "row" and the directive "app-cell"

@Directive({
  selector: 'app-cell,td[app-cell]',
})
export class CellDirective {
  @HostBinding('class') 
  @Input() name;
  @Input() title;
  constructor() {}
}

//and
@Directive({
  selector: '[row]',
})
export class RowDirective  {
  constructor(public templateRef: TemplateRef<any>) {}
}

The app-table component like

export class AppTableComponent implements AfterViewInit {
  @ContentChildren(CellDirective) cellDatas: QueryList<CellDirective>;
  @ContentChildren(RowDirective) cells: QueryList<RowDirective>;

  @ViewChildren(MatCellDef) cellDefs:QueryList<MatCellDef>
  @ViewChild(MatTable) matTable:MatTable<any>

  @Input() source

  dataSource:any[];
  displayedColumns:string[];
  templates:TemplateRef<any>[]
  titles: any[] = [];
  constructor(private cdr: ChangeDetectorRef) {}

  ngAfterViewInit() {
      //first we give value to displayedColumns
      setTimeout(()=>{
        this.displayedColumns=this.cells.map((x,i)=>'col' i)
      })

      //we subscribe to cellDefs to asign the TemplateRef
      //from our *row templates
      this.cellDefs.changes.pipe(take(1)).subscribe(res=>{
        const templates=this.cells.toArray().map(x=>x.templateRef);
        res.forEach((x,i)=>{
          x.template=templates[i]
        })
        setTimeout(()=>{
          this.dataSource=this.source
        })
      })

      //we get the "titles"
      this.cellDatas.changes.pipe(take(1)).subscribe((res) => {
        setTimeout(()=>{
          this.titles = res
          .map((x) => x.title)
          .reduce((a, b) => (a.indexOf(b) < 0 ? [...a, b] : a), []);
        })
      });
  }
}

NOTE: ther're a severals setTimeouts to avoid the error "afterchecked", if I'll get avoid, I 'll update the answer

The stackblitz

  • Related