Programmatically collapsing a mat-expansion-panel gets it out-of-sync with expansion by mouse clicks

Viewed 581

I have a menu that contains an expandable panel. I would like the menu to open up with that panel collapsed, regardless of whether or not it had been left expanded the last time the menu was open. So I catch both the menuOpened and menuClosed events from my mat-menu, and call MatExpansionPanel.close().

Stackblitz demo and code here: https://stackblitz.com/edit/material-expansion-panel-issue?file=src/app/app.component.ts

To see the problem, click the menu open, expand the panel, click the menu closed, and open the menu once again.

The problem you'll see is that, after doing this, the expansion panel gets stuck in a state where it takes three clicks to reopen it, instead of just one.

  1. The first click sets the internal state of MatExpansionPanel.expanded to true, but graphically, it doesn't actually expand. Click 1
  2. The second click closes what's already (visually) closed anyway.Click 2
  3. With the third click, everything is finally back in sync, and the panel actually expands.Click 3

As an alternative, I've tried:

  • Using the expanded input of the mat-expansion-panel in HTML, rather than calling close() in my TypeScript code. That doesn't help.
  • I've tried calling close() only once, when the menu is closed. Same problem.
  • I've tried calling close() only once, when the menu is opened. That sort-of fixes the problem, but there's an ugly visible twitch of seeing the previously-opened panel flicker shut as the menu opens.
  • I've tried *ngIf-ing the mat-expansion-panel in and out of existence as the menu opens and closes. That another almost-fix, with bad visual effects as a result.

I suspect this is a bug in Material, not something I'm doing wrong, but if it's my doing, it would be great if someone can point out my error. If it is a Material bug, it would be great if anyone has a workaround.

import { Component, ViewChild } from '@angular/core';
import { MatExpansionPanel } from '@angular/material/expansion';

@Component({
  selector: 'my-app',
  templateUrl: './app.component.html',
  styleUrls: [ './app.component.scss' ]
})
export class AppComponent  {
  @ViewChild('expander', { static: true }) expander: MatExpansionPanel

  onOpen(): void {
    this.expander.close();
  }

  onClose(): void {
    this.expander.close();
  }
}
<div>What is the supposed expansion state right now?: {{expander?.expanded}}</div><br>

<div [matMenuTriggerFor]="menu" class="menu-button"
  (menuOpened)="onOpen()" (menuClosed)="onClose()">
  <mat-icon>menu</mat-icon>Click here
</div>
<mat-menu #menu="matMenu" class="menu">
  <mat-accordion (click)="$event.stopPropagation()">
    <mat-expansion-panel #expander>
      <mat-expansion-panel-header>
        <mat-panel-title>Expandable Stuff</mat-panel-title>
      </mat-expansion-panel-header>
      <div>Expanded line 1</div>
      <div>Expanded line 2</div>
    </mat-expansion-panel>
  </mat-accordion>
   <div>Other panel content</div>
</mat-menu>

UPDATE:

I've found an improved workaround. Not a full solution, but one that only produces a slight visual twitch that's not too offensive.

  1. Surround the expandable content with an *ngIf as follows, rather than *ngIf the entire panel.
<ng-container *ngIf="open">
  <div>Expanded line 1</div>
  <div>Expanded line 2</div>
</ng-container>
  1. Track the open/close of the menu like this:
  onOpen(): void {
    this.userExpander.close();
    setTimeout(() => this.open = true, 400);
  }

  onClose(): void {
    this.open = false;
  }

The only visible defect this way is the menu shrinking a few pixels after being opened, and only if the panel had been left in the expanded state.

0 Answers
Related