angularng-templatemat-stepperangular-material-11

Angular Material Stepper custom icons ng-template has undefined context variable


I am trying to use an ng-template with the matStepperIcon property to override the default Angular Material's matStepper's icons. I also want to pass some data, and I tried using ng-container and *ngTemplateOutlet, however it only partially works.

As you can see from the following code, I would expect the print function to always print a defined value, however, after correctly printing the seven IDs of the steps it prints undefined seven times. Why is that and how do I prevent that from happening?

<mat-horizontal-stepper labelPosition="bottom">
  <ng-container *ngFor="let step of form.steps">
    <ng-template #ref let-step="id" matStepperIcon="edit">
      <mat-icon>clear</mat-icon>
      {{ print(step) }}
    </ng-template>
    
    <ng-template #ref let-step="id" matStepperIcon="number">
      <mat-icon>clear</mat-icon>
      {{ print(step) }}
    </ng-template>

    <ng-template #ref let-step="id" matStepperIcon="done">
      <mat-icon>clear</mat-icon>
      {{ print(step) }}
    </ng-template>
    
    <ng-container *ngTemplateOutlet="ref; context: step"></ng-container>

    <mat-step>
      <ng-template matStepLabel>
        {{ step.id }}:
        {{ translateService.currentLang === "it" ? step.name : step.nameEn }}
      </ng-template>

      <!-- Step content-->
      </mat-step>
  </ng-container>
</mat-horizontal-stepper>

Link to StackBlitz example

Changing my code to the following, on the other hand, causes the print function to print all the correct IDs but it also prints the ID of the last step 8 times.

<mat-horizontal-stepper labelPosition="bottom">
  <ng-container *ngFor="let step of form.steps">
    <ng-template #ref matStepperIcon="edit">
      {{ print(step.id) }}
      <mat-icon>clear</mat-icon>
    </ng-template>

    <ng-template #ref matStepperIcon="number">
      {{ print(step.id) }}
      <mat-icon>clear</mat-icon>
    </ng-template>

    <ng-template #ref matStepperIcon="done">
      {{ print(step.id) }}
      <mat-icon>clear</mat-icon>
    </ng-template>

    <ng-container *ngTemplateOutlet="ref; context: step"></ng-container>

    <mat-step>
      <ng-template matStepLabel>
        {{ step.id }}:
        {{ translateService.currentLang === "it" ? step.name : step.nameEn }}
      </ng-template>

      <!-- Step content-->
    </mat-step>
  </ng-container>
</mat-horizontal-stepper>

Solution

  • If you want to use custom icons with MatStepper you should do several things:

    We could disable displayDefaultIndicatorType like

    providers: [{
       provide: STEPPER_GLOBAL_OPTIONS, useValue: {displayDefaultIndicatorType: false}
    }]
    

    But it won't help since Angular Material has predefined logic to show specific icon depending on internal step state https://github.com/angular/components/blob/28c36f8a02f72e51a4d6c6a797e2f913e5dede9b/src/cdk/stepper/stepper.ts#L442-L465

    So, you can override base logic like here https://github.com/angular/components/issues/18307

    @ViewChild(MatHorizontalStepper, { static: true }) stepper: MatHorizontalStepper;
    
    ngOnInit() {
      this.stepper._getIndicatorType = (i, state) => state || 'number';
    }
    

    Forked Stackblitz