Johnny Cusack
Johnny Cusack

Reputation: 123

Dynamically rendered mat-expansion-panel is not working as intended

I need to dynamically render a list of expansion panels inside an accordion.

The markup is pretty straight forward:

<mat-accordion *ngIf="currentVenue">
  <mat-expansion-panel *ngFor="let gig of currentVenue.gigs" expanded="false">
    <mat-expansion-panel-header>
      <mat-panel-title>
        {{gig.name}}
      </mat-panel-title>
      <mat-panel-description>
        Type your name and age
      </mat-panel-description>
    </mat-expansion-panel-header>

  </mat-expansion-panel>
</mat-accordion>

Unfortunately this results in an unusable control. The header is not opening the panel and the whole accordion functionality is messed up as well. I need to click outside of the control and then randomly it opens one child expansion panel (or not).

If I in turn use a "static" approach (I copied this from the samples' code), everything works as intended:

<mat-accordion>
  <mat-expansion-panel>
    <mat-expansion-panel-header>
      <mat-panel-title>
        Personal data
      </mat-panel-title>
      <mat-panel-description>
        Type your name and age
      </mat-panel-description>
    </mat-expansion-panel-header>

    <mat-form-field>
      <input matInput placeholder="First name">
    </mat-form-field>

    <mat-form-field>
      <input matInput placeholder="Age">
    </mat-form-field>
  </mat-expansion-panel>
  [...]
</mat-accordion>

My guess is, that it has to do with *ngIf and the way the controls are created.

I'm using Angular Material 6.4.7 and Angular 6.1.10

Upvotes: 2

Views: 12517

Answers (1)

joh04667
joh04667

Reputation: 7427

You're right about *ngIf doing some funny stuff here. It's a structural directive, so at a low level, Angular renders it differently from other components. This rendering can interfere with other structural directives, so Angular only allows a template to be bound to one structural directive at a time.

But good news! The asterisk in the name is just syntactic sugar for what structural directives really do. If we de-sugar the name and bind it to a template explicitly, enveloping the accordion itself, Angular will be able to render the nested directives with this template context instead of using the component's template:

  <ng-template [ngIf]="currentVenue">
    <mat-accordion>
      <mat-expansion-panel *ngFor="let gig of currentVenue.gigs" expanded="false">
        <mat-expansion-panel-header>
          <mat-panel-title>
            {{gig.name}}
          </mat-panel-title>
          <mat-panel-description>
            Type your name and age
          </mat-panel-description>
        </mat-expansion-panel-header>

      </mat-expansion-panel>
    </mat-accordion>
  </ng-template>

Note how the ngIf is now bound like a regular directive. This can only be used on ng-template tags. Without the asterisk, Angular won't try to bind a different template to it and nested directives will work.

We could also give the repeated items their own ng-template and ngForOf directives, but the syntax with [ngIf] is much cleaner.

Upvotes: 2

Related Questions