4

I'm trying to create a reusable mat table component with pagination, sorting and search. the reusable component that I made displays some simple columns , and projects some custom columns depending on the use case through <ng-content></ngcontent>.

the problem is that sorting doesn't work for the custom projected columns through <ng-content></ngcontent>.

I get that I have two matSort instances and the one on the reusable table is the only one registered by the sort header .

is there any solution to make sorting on the custom projected columns works ? or otherwise any other way to use custom columns which would make sorting works on them ?

the reusable table html :

    <table mat-table [dataSource]="data" class="mat-elevation-z8" matSort [matSortActive]="sortActiveColumn" matSortDisableClear>

    <!--- 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]="column.name" *ngFor="let column of genericColumns">
        <th mat-header-cell *matHeaderCellDef [mat-sort-header]=" column.sortable ? column.name : null "> {{ column.label }} </th>
        <td mat-cell *matCellDef="let item"> {{item[column.name]}} </td>
    </ng-container>

    <ng-content></ng-content>

    <ng-container matColumnDef="actions">
        <th mat-header-cell *matHeaderCellDef mat-sort-header="actions"></th>
        <td mat-cell *matCellDef="let item">
            <div class="flex gap-4">
                <button mat-mini-fab color="primary" class="text-white shadow-none" (click)="showClicked(item)" *ngIf="actions.includes('show')">
                    <mat-icon>open_in_new</mat-icon>
                </button>
                <button mat-mini-fab color="accent" class="text-white shadow-none" (click)="editClicked(item)" *ngIf="actions.includes('edit')">
                    <mat-icon>edit</mat-icon>
                </button>
                <button mat-mini-fab color="warn" class="shadow-none" (click)="deleteClicked(item)" *ngIf="actions.includes('delete')">
                    <mat-icon>delete</mat-icon>
                </button>
            </div>
        </td>
    </ng-container>

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

some custom columns use cases :

    <app-data-table [data]="retours" [total]="totalRetours" [columns]="columns" (dataTableEvent)="datatableEvent($event)" [actions]="['show','edit','delete']" (onDelete)="remove($event)" (onShow)="show($event)" (onEdit)="edit($event)" matSort matSortActive="created_at">
        <ng-container matColumnDef="users.firstname">
            <th mat-header-cell *matHeaderCellDef mat-sort-header="users.firstname">
                Utilisateur </th>
            <td mat-cell *matCellDef="let item"> {{ item.user.full_name }} </td>
        </ng-container>
        <ng-container matColumnDef="clients.firstname">
            <th mat-header-cell *matHeaderCellDef mat-sort-header="clients.firstname">
                Client </th>
            <td mat-cell *matCellDef="let item"> {{ item.client.full_name }} </td>
        </ng-container>
        <ng-container matColumnDef="total">
            <th mat-header-cell *matHeaderCellDef mat-sort-header="total">
                Somme finale </th>
            <td mat-cell *matCellDef="let item"> {{ item.total_ttc | money }} </td>
        </ng-container>
        <ng-container matColumnDef="payment.status">
            <th mat-header-cell *matHeaderCellDef mat-sort-header="payment.status">
                Status du paiement </th>
            <td mat-cell *matCellDef="let item"> {{ item.payment.status }} </td>
        </ng-container>
        <ng-container matColumnDef="created_at">
            <th mat-header-cell *matHeaderCellDef mat-sort-header="created_at">
                Crée à </th>
            <td mat-cell *matCellDef="let item"> {{ item.created_at | momentdate }} </td>
        </ng-container>
    </app-data-table>
<app-data-table [data]="activityTypes" [total]="totalActivityTypes" [columns]="ActivityTypesTableColumns" (dataTableEvent)="datatableEvent($event)" [actions]="['edit','delete']" (onDelete)="remove($event)" (onEdit)="edit($event)" matSort>
    <ng-container matColumnDef="name">
        <th mat-header-cell *matHeaderCellDef mat-sort-header="name"> Nom Type d'activité </th>
        <td mat-cell *matCellDef="let item"> Fr :{{ item.name.fr }} - En : {{ item.name.en }} </td>
    </ng-container>
    <ng-container matColumnDef="color">
        <th mat-header-cell *matHeaderCellDef mat-sort-header="color"> Coleur </th>
        <td mat-cell *matCellDef="let item">
            <div class="w-8 h-8 rounded-full border-black border" [ngStyle]="{'background-color': item.color}"></div>
        </td>
    </ng-container>
    <ng-container matColumnDef="created_at">
        <th mat-header-cell *matHeaderCellDef mat-sort-header="created_at">
            Crée à </th>
        <td mat-cell *matCellDef="let item"> {{ item.created_at | momentdate }} </td>
    </ng-container>
</app-data-table>

1 Answer 1

1

I'm late to the party but in case someone like ends up here for this issue, here is how I solved this.

I met a similar issue and I found a little hack that does the job for me. My issue was that I could not have something like this:

Parent:

<app-table-v2 [dataSource]="dataSource">
    <ng-container matColumnDef="id">
        <th mat-header-cell *matHeaderCellDef mat-sort-header>
            {{ 'tables.tyreManufacturers.attributes.id' | translate | uppercase }}
        </th>
        <td mat-cell *matCellDef="let element">
            {{ element.id }}
        </td>
    </ng-container>
</app-table-v2>

Component:

<table mat-table [dataSource]="dataSource" matSort (matSortChange)="onSort($event)">
    <tr mat-header-row *matHeaderRowDef="displayColumns"></tr>
    <tr mat-row *matRowDef="let row; columns: displayColumns"></tr>
</table>

Instead I added a wrapper ng-container with the matSort directive in my parent component and then using a @ContentChildren(MatSort) I listen from my table component to sortChanges. Here is how it looks like now:

Parent:

<app-table-v2 [dataSource]="dataSource">
    <ng-container matSort>  <!-- ADD THIS -->
        <ng-container matColumnDef="id">
            <th mat-header-cell *matHeaderCellDef mat-sort-header>
                {{ 'tables.tyreManufacturers.attributes.id' | translate | uppercase }}
            </th>
            <td mat-cell *matCellDef="let element">
                {{ element.id }}
            </td>
        </ng-container>
    </ng-container>
</app-table-v2>

Component:

<table mat-table [dataSource]="dataSource">
    <tr mat-header-row *matHeaderRowDef="displayColumns"></tr>
    <tr mat-row *matRowDef="let row; columns: displayColumns"></tr>
</table>

And in the component typescript:

@ContentChildren(MatSort) childSort!: QueryList<MatSort>;
@ContentChildren(MatColumnDef) columnDefs!: QueryList<MatColumnDef>;

ngAfterContentInit() {
    const sort = this.childSort.get(0);

    if (sort) sort.sortChange.subscribe((sort: Sort) => this.onUpdateSort(sort));
    
    this.displayColumns = [];
    this.columnDefs.forEach(columnDef => {
        this.table.addColumnDef(columnDef);
        this.displayColumns.push(columnDef.name);
    });
}

Let's be honest, this is still a hack. A well designed component should not require to do this, but this works and I could not find a better solution.

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

Comments

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.