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
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');
});
}
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"