Angular Material table expandable table row not expanding?


I’ve been staring at this for too long, and I can’t figure it out.

There’s a good example of an Angular Material table with an expandable row in this stackoverflow answer. I can plug the example into my app, and it works correctly.

I’m failing to apply the example to my data. I think my problem is in the HTML, since the table renders, the expanded row CSS seems to get applied (the row delineation disappears on clicking the first row), but the expansion row does not appear.

Does anyone see the problem? Thanks in advance.

Here’s a StackBlitz of what I have. I’ve put the working example from the linked SO answer in side by side with my broken example


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

import { animate, state, style, transition, trigger } from '@angular/animations';

import { Tweet } from '../tweet';
import { TweetService } from '../tweet.service';
import { TweetsDataSource } from '../tweets.datasource';
import { data } from '../mock-tweets';

selector: 'app-tweets-table',
templateUrl: './tweets-table.component.html',
styleUrls: ['./tweets-table.component.css'],
animations: [
    trigger('detailExpand', [
    state('collapsed', style({ height: '0px', minHeight: '0', visibility: 'hidden' })),
    state('expanded', style({ height: '*', visibility: 'visible' })),
    transition('expanded <=> collapsed', animate('225ms cubic-bezier(0.4, 0.0, 0.2, 1)')),
export class TweetsTableComponent implements OnInit {
// Data
displayedColumns = [
    'createdTime', 'tweetText', 'favoriteCount'
tweets: Tweet[];
expandedTweet: any;
dataSource: TweetsDataSource|null;

constructor(private tweetService: TweetService) {

ngOnInit() {

getTweets(): void {
    // this.tweets = this.tweetService.getTweets(this.pageIndex, this.pageSize);
    this.tweets = data;
    this.dataSource = new TweetsDataSource(this.tweets);

isExpansionDetailRow = (i: number, row: Object) => {



<div class="example-container mat-elevation-z8">
    <mat-table #table [dataSource]="dataSource">

    <!--- Note that these columns can be defined in any order.
            The actual rendered columns are set as a property on the row definition" -->

    <!-- Time Column -->
    <ng-container matColumnDef="createdTime">
        <mat-header-cell *matHeaderCellDef> Tweet Time </mat-header-cell>
        <mat-cell *matCellDef="let tweet"> {{tweet.created_time}} </mat-cell>

    <!-- Text Column -->
    <ng-container matColumnDef="tweetText">
        <mat-header-cell *matHeaderCellDef> So Texty </mat-header-cell>
        <mat-cell *matCellDef="let tweet"> {{tweet.text}} </mat-cell>

    <!-- Favs Column -->
    <ng-container matColumnDef="favoriteCount">
        <mat-header-cell *matHeaderCellDef> Flava Favs </mat-header-cell>
        <mat-cell *matCellDef="let tweet"> {{tweet.favorite_count}} </mat-cell>

    <!-- Expanded Content Column - The detail row is made up of this one column -->
    <ng-container matColumnDef="expandedDetail">
        <mat-cell *matCellDef="let detail"> 
        Walk before you run. Here's more text: {{detail.tweet.text}}

    <mat-header-row *matHeaderRowDef="displayedColumns"></mat-header-row>
    <mat-row *matRowDef="let row; columns: displayedColumns;"
            [class.expanded]="expandedTweet == row"
            (click)="expandedTweet = row"></mat-row>
    <mat-row *matRowDef="let row; columns: ['expandedDetail']; when: isExpansionDetailRow"
            [@detailExpand]="row.element == expandedTweet ? 'expanded' : 'collapsed'"
            style="overflow: hidden"> 

I think my DataSource object is okay. However, I’m having a hard time getting a full understanding of MatDataSource, so I’ll include that, too.


import { Component, OnInit } from '@angular/core';
import { DataSource } from '@angular/cdk/collections';
import { Observable, of } from 'rxjs';

import { Tweet } from './tweet';
import { TweetService } from './tweet.service';

export class TweetsDataSource extends DataSource<Tweet> {
    constructor(private tweets: Tweet[]) {

    connect(): Observable<Tweet[]> {
        const rows = [];
        this.tweets.forEach(element => rows.push(element, { detailRow: true, element }));
        console.log('dataset rows:')
        return of(this.tweets);

    disconnect() { }



In the connect method you are returning the wrong stuff. It should return rows instead of this.tweets.

connect(): Observable<Tweet[]> {
    const rows = [];
    this.tweets.forEach(tweet => rows.push(tweet, { detailRow: true, tweet }));
    console.log('dataset rows:')
    return of(rows);


This also has a error the method isExpansionDetailRow should return a boolean value true or false (Not do nothing). So add the return. The original code does not need the return because it just uses one line without the brackets.

If an arrow function is simply returning a single line of code, you can omit the statement brackets and the return keyword. This tells the arrow function to return the statement. Reference =>

  isExpansionDetailRow = (i: number, row: Object) => {
    return row.hasOwnProperty('detailRow');

If you are interested in another way of doing this you can refer to this issue.

MatTable Expand Collapse Icon issue on pagination and sort

Answered By – Ho Wei Lip

Answer Checked By – Dawn Plyler (AngularFixing Volunteer)

Leave a Reply

Your email address will not be published.