As mentioned here by Andrew Seguin this is already feasible out of the box: using the when predicate.
See this example: https://stackblitz.com/edit/angular-material-expandable-table-rows (thx to Lakston)

Inside of the mat-table tag you have to use the mat-row component with a matRipple directive. When you click on a row the row element will be assigned to the expandedElement variable:
<mat-row *matRowDef="let row; columns: displayedColumns;"
matRipple
class="element-row"
[class.expanded]="expandedElement == row"
(click)="expandedElement = row">
</mat-row>
But now we have to add our expanded row, that is hidden by default and will be shown if the user clicks on the row above:
<mat-row *matRowDef="let row; columns: ['expandedDetail']; when: isExpansionDetailRow"
[@detailExpand]="row.element == expandedElement ? 'expanded' : 'collapsed'"
style="overflow: hidden">
</mat-row>
Important is here the already mentioned when predicate. This calls a isExpansionDetailRow function that is defined in the component itself and checks if the row has a detailRow property:
isExpansionDetailRow = (row: any) => row.hasOwnProperty('detailRow');
Since RC0 the first param is the index:
isExpansionDetailRow = (i: number, row: any) => row.hasOwnProperty('detailRow');
If you want to have an expanded view for every row, you have to add an "ExpansionDetailRow" identified by the detailRow property for every row like this:
connect(): Observable<Element[]> {
const rows = [];
data.forEach(element => rows.push(element, { detailRow: true, element }));
return Observable.of(rows);
}
If you would log the rows variable to the console output, it will look like this:

EDIT: COMPLETE EXAMPLE USING DIRECTIVE
Mat Table expandable rows (sorting, pagination and filtering)