0

Objectif: Creating a component app-table which would contain some definitions of columns that are common to multiple tables + giving inside the bracks some more definitions from the parent to the child

parent component

<app-table [data]="users" [displayedColumns]="displayedColumns" [busy]="busyLoading">
    <!-- action -->
<ng-container matColumnDef="actions">
        <th mat-header-cell *matHeaderCellDef>{{'COMMON.ACTION'}}</th>
        <td mat-cell *matCellDef="let row">
        <button type="button" [routerLink]="['/users/' + row.id]">edit</button>
        </td>
    </ng-container>

</app-table>

app-table component

<table mat-table [dataSource]="dataSource">
    <ng-container matColumnDef="firstName">
        <th scope="col" mat-header-cell *matHeaderCellDef>{{'USER.FIRST_NAME' | translate}}</th>
        <td mat-cell *matCellDef="let row">{{row.firstName}}</td>
    </ng-container>

<ng-container matColumnDef="lastName">
        <th scope="col" mat-header-cell *matHeaderCellDef>{{'USER.LASTE_NAME' | translate}}</th>
        <td mat-cell *matCellDef="let row">{{row.lastName}}</td>
    </ng-container>

    <!-- what I'm trying to do -->
<ng-content></ng-content>

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

</table>

the error I get:

ERROR Error: Could not find column with id "actions"


ADD:

This is a new approche

parent HTML

    <!-- parent.component.html -->
<h1>Parent Component</h1>
<app-user-list [dataSource]="users" [displayedColumns]="displayedColumns">
    <!-- Custom columns for UserListComponent -->
    <ng-container matColumnDef="lastname">
        <th mat-header-cell *matHeaderCellDef>Last Name</th>
        <td mat-cell *matCellDef="let user">{{ user.lastname }}</td>
    </ng-container>
</app-user-list>

typescript

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

interface User {
    id: number;
    firstname: string;
    lastname: string;
}

@Component({
    selector: 'app-parent',
    templateUrl: './parent.component.html',
    styleUrls: ['./parent.component.scss']
})
export class ParentComponent {

    users: User[] = [
        { id: 1, firstname: 'John', lastname: 'Doe' },
        { id: 2, firstname: 'Jane', lastname: 'Smith' },
        { id: 3, firstname: 'Alice', lastname: 'Johnson' },
        { id: 4, firstname: 'Bob', lastname: 'Brown' },
    ];
    displayedColumns: string[] = [
        'id',
        'firstname',
        'lastname'
    ];

    constructor() { }
}

child html

<!-- user-list.component.html -->
<mat-card>
    <mat-card-header>
        <mat-card-title>User List</mat-card-title>
    </mat-card-header>
    <mat-card-content>
        <table mat-table [dataSource]="dataSource">
            <!-- Default columns (ID and First Name) -->
            <ng-container matColumnDef="id">
                <th mat-header-cell *matHeaderCellDef>ID</th>
                <td mat-cell *matCellDef="let user">{{ user.id }}</td>
            </ng-container>
            <ng-container matColumnDef="firstname">
                <th mat-header-cell *matHeaderCellDef>First Name</th>
                <td mat-cell *matCellDef="let user">{{ user.firstname }}</td>
            </ng-container>

            <!-- Custom columns (added using ng-content) -->
            <ng-content select="[matColumnDef='lastname']"></ng-content>

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

typescript

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


@Component({
    selector: 'app-user-list',
    templateUrl: './user-list.component.html',
    styleUrls: ['./user-list.component.scss']
})
export class UserListComponent {
    @Input() dataSource: any;
    @Input() displayedColumns: string[] = [];

    constructor() { }
}

How can I display the column lastname without having to define it inside the userList

Python
  • 31
  • 6
  • Do some one has a solution ? – Python Jul 20 '23 at 07:46
  • well to resolve your first error displayedColumns: string[] = ['id', 'firstname'];. you forgot to include 'actions' here. You should do the same for the lastname. But you will get an error if you do not have the matColumnDef for it. You could however just have the lastname in the html and not have it in the displayedColumns arrary. – Luigi Woodhouse Jul 24 '23 at 13:00
  • @LuigiWoodhouse I just reworked the code I forgot to update some – Python Jul 24 '23 at 13:17

1 Answers1

1

You can use the function: addColumnDef of a MatTable

So the only is defined your ColumnDef outside the app-user-list and pass as input

You get it using ViewChildren

  @ViewChildren(MatColumnDef) columns:QueryList<MatColumnDef>

And define like:

<table-basic-example
  [data]="dataSource"
  [columnAdd]="columns"
  [displayedColumns]="displayedColumns"
>
</table-basic-example>

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

<ng-container matColumnDef="actions">
  <th mat-header-cell *matHeaderCellDef>Actions</th>
  <td mat-cell *matCellDef="let row">
    <button type="button" (click)="edit(row)">edit</button>
  </td>
</ng-container>

The only is, in afterViewInit in your componnet add this columnsDef and add the columns

  @Input() columnAdd: QueryList<MatColumnDef>;
  @ViewChild(MatSort) sort: MatSort;
  @ViewChild(MatTable) table: MatTable<any>;

  ngAfterViewInit() {
    this.dataSource.sort = this.sort;
    setTimeout(() => {
      this.columnAdd.forEach((x) => {
        this.table.addColumnDef(x);
      });

      this.displayedColumns2.push('name');
      this.displayedColumns2.push('actions');
    });
  }

a stackblitz

NOTE: Really you can add the columns definitions inside or outside the tag of the component

Update if we put the matColumnDef outside the component we can use a *ngIf to not show the component until Angular get it

<table-basic-example *ngIf="columns && columns.length"..>
</table-basic-example>

<ng-container matColumnDef="name">
  ..
</ng-container>

<ng-container matColumnDef="actions">
  ..
</ng-container>

To check:
{{columns?.length || 'I am not get it'}}

Avoid the ngAfterChecked error required a work-aroud. The problem is that "columns" are ready only in ngAfterViewInit, so Angular give this error always. We can use a variable

yet:boolean=false

And in parent in ngOnInit use a setTimeout()

ngOnInit(){
  setTimeout(()=>{
     this.yet=true;
  })
}

And use a *ngIf="yet"

Eliseo
  • 50,109
  • 4
  • 29
  • 67
  • 1
    Thanks for the answer @Eliseo. By trying out the stackblitz example, the last 2 columns headers are not displayed, the '' part should be updated to use displayedColumns2 – Gérôme Grignon Jul 25 '23 at 10:24
  • @GérômeGrignon, thanks for the advise! – Eliseo Jul 25 '23 at 10:38
  • I have tried it on stackblitz https://stackblitz.com/edit/angular-4wvact-jylb43?file=src%2Fapp%2Fapp.main.ts,src%2Fapp%2Fapp.main.html,src%2Fapp%2Fapp-table.ts,src%2Fapp%2Fapp-table.html,package.json and it works well but when I add it to my code it doens't work any idea ? parent: https://prnt.sc/yagvQSCvR2Ke child: https://prnt.sc/Q0Vpak124X7P the error: https://prnt.sc/r2LSs82zZDzY it display some times the columns but it's randomly displyed – Python Jul 26 '23 at 07:44
  • @Python, I update the answer to not show the table until Angular get the different "matColumnDef". NOTE: The order of the columns are the order of the array displayedColumns. – Eliseo Jul 26 '23 at 08:23
  • thanks, and yes this I know about the order I would see to upgrade later the code to be able to set all the columns inside displayedColumns I will look at it tomorrow morning good evening – Python Jul 26 '23 at 14:28