Nick Bellamy
Nick Bellamy

Reputation: 43

*ngIf doesn't work in an an Angular Material table when using an observable

I'm trying to use an Angular Material table with expandable rows, to show information only if the screen width is below a particular breakpoint. I'm using an observable isHandSetPortrait$, that I'm subscribing to in my HTML, to determine whether the breakpoint has been reached.

The table has two columns (Name, and Info). Name is displayed whether isHandsetPortrait is true or false, and Info is only displayed when isHandsetPortrait$ is false. This works as expected.

In the expandable row, I have an *ngIf that uses the same observable isHandsetPortrait$, to display text when the output of isHandsetPortrait$ is true. However, this doesn't appear to be working.

table-expandable-rows-example.html

<!-- *ngIf works here -->
  <div *ngIf="isHandsetPortrait$ | async">ngIf works here</div>
  <div class="mat-elevation-z8">
    <table mat-table [dataSource]="dataSource" matSort matSortActive="priority" matSortDirection="desc"
           multiTemplateDataRows>


      <!-- Name Column -->
      <ng-container matColumnDef="name">
        <th mat-header-cell *matHeaderCellDef mat-sort-header> Name</th>
        <td mat-cell *matCellDef="let row"> {{row.name}}</td>
      </ng-container>

      <!-- Info Column -->
      <ng-container matColumnDef="info" >
        <th mat-header-cell *matHeaderCellDef></th>
        <td mat-cell *matCellDef="let row" class="info-cell">
          Info
        </td>
      </ng-container>

      <!-- Expanded Content Column - The detail row is made up of this one column that spans across all columns -->
      <ng-container matColumnDef="expandedDetail">
        <td mat-cell *matCellDef="let element" [attr.colspan]="displayedColumns.length">
          <div class="expanded-content" [@detailExpand]="element == expandedElement ? 'expanded' : 'collapsed'">
            Additional text....
            <!-- *ngIf does not work here -->
            <div *ngIf="isHandsetPortrait$ | async">ngIf does not work here</div>
          </div>
        </td>
      </ng-container>

      <tr mat-header-row *matHeaderRowDef="getDisplayedColumns(isHandsetPortrait$ | async)"></tr>
      <tr mat-row *matRowDef="let element; columns: getDisplayedColumns(isHandsetPortrait$ | async);"
          class="example-element-row" [class.example-expanded-row]="expandedElement === element"
          (click)="expandedElement = expandedElement === element ? null : element"></tr>
      <tr mat-row *matRowDef="let row; columns: ['expandedDetail']" class="example-detail-row"></tr>
    </table>
  </div>

table-expandable-rows-example.ts

import { Component, OnInit, ViewChild } from '@angular/core';
import { MatPaginator, MatSort, MatTableDataSource } from '@angular/material';
import { animate, state, style, transition, trigger } from '@angular/animations';
import { from, Observable, of, pipe } from 'rxjs';
import { concatMap, delay, map, share } from 'rxjs/operators';
import { BreakpointObserver, Breakpoints } from '@angular/cdk/layout';

/**
 * @title Table with expandable rows
 */
@Component({
  selector: 'table-expandable-rows-example',
  styleUrls: ['table-expandable-rows-example.css'],
  templateUrl: 'table-expandable-rows-example.html',
animations: [
    trigger('detailExpand', [
      state('collapsed', style({ height: '0px', minHeight: '0' })),
      state('expanded', style({ height: '*' })),
      transition('expanded <=> collapsed', animate('225ms cubic-bezier(0.4, 0.0, 0.2, 1)'))
    ]),
  ]
})
export class TableExpandableRowsExample {

    public isHandsetPortrait$: Observable<boolean> = this.breakpointObserver.observe(Breakpoints.HandsetPortrait)
    .pipe(
      map(orientationState => orientationState.matches),
      share()
    );

  private chatRequests = [
    {
      name: 'John Wayne'
    },
    {
      name: 'Oscar Wilde'
    },
    {
      name: 'Eric Cantona'
    }
  ];

  dataSource = new MatTableDataSource(this.chatRequests);

  displayedColumns: { def: string, isMobile: boolean }[] = [
    {
      def: 'name',
      isMobile: true
    },
    {
      def: 'info',
      isMobile: false
    }
  ];

  constructor(private breakpointObserver: BreakpointObserver) { }


  public getDisplayedColumns(isHandsetPortrait: boolean) {
    return this.displayedColumns
      .filter(cd => !isHandsetPortrait || cd.isMobile)
      .map(cd => cd.def);
  }

}

I've put together a StackBlitz project that should highlight the issue I'm having.

The *ngIf works outside of the table (Line 2 of the html template), but does not work inside the table (Line 28 of the html template). How can I make the second *ngIf work?

Upvotes: 3

Views: 1936

Answers (1)

Fateh Mohamed
Fateh Mohamed

Reputation: 21377

you have to use shareReplay to make it works, using share nothing will happen for the second subscriber

public isHandsetPortrait$: Observable<boolean> = this.breakpointObserver.observe(Breakpoints.HandsetPortrait)
.pipe(
  map(orientationState => orientationState.matches),
  shareReplay(1) // here
);

https://stackblitz.com/edit/angular-ml4vym-71mba5

Upvotes: 2

Related Questions