I have a solution but it is not perfect.
<table mat-table [dataSource]="dataSource"
multiTemplateDataRows class="mat-elevation-z8">
<ng-container matColumnDef="{{column}}"
*ngFor="let column of columnsToDisplay">
<th mat-header-cell *matHeaderCellDef>
{{column !== 'action' ? column : ''}}
</th>
<ng-container *ngIf="column !== 'action'">
<td mat-cell *matCellDef="let aliasesData">
<span>{{aliasesData[column]}}</span>
</td>
</ng-container>
<ng-container *ngIf="column === 'action'">
<td mat-cell *matCellDef="let aliasesData">
<span>
<button>Edit</button>
<button>Delete</button>
</span>
</td>
</ng-container>
</ng-container>
<tr mat-header-row *matHeaderRowDef="columnsToDisplay"></tr>
<tr mat-row *matRowDef="let row; columns: columnsToDisplay;"></tr>
</table>
You can have a ngIf binding to check for the column action and show buttons only for it alone.
Or Have 2 column display arrays
columnsToDisplay = ['id', 'domain_id', 'source'];
columnsForConfig = ['id', 'domain_id', 'source', 'action'];
and use one for rendering and the other for the configuration at the bottom and have actions as a separate section after the ngFor binding.
<table mat-table [dataSource]="dataSource" multiTemplateDataRows class="mat-elevation-z8">
<ng-container matColumnDef="{{column}}" *ngFor="let column of columnsToDisplay">
<th mat-header-cell *matHeaderCellDef>
{{column}}
</th>
<td mat-cell *matCellDef="let aliasesData">
<span>{{aliasesData[column]}}</span>
</td>
</ng-container>
<ng-container matColumnDef="action">
<th mat-header-cell *matHeaderCellDef>
actions
</th>
<td mat-cell *matCellDef="let aliasesData">
<button>Edit</button>
<button>Delete</button>
</td>
</ng-container>
<tr mat-header-row *matHeaderRowDef="columnsForConfig"></tr>
<tr mat-row *matRowDef="let row; columns: columnsForConfig;"></tr>
</table>