Components: feat(stepper): support lazy rendering of steps

Created on 24 Aug 2018  Â·  13Comments  Â·  Source: angular/components

Bug, feature request, or proposal:

feature request-ish

What is the expected behavior?

Each step in the stepper should have the option to be lazily rendered

What is the current behavior?

Currently every step is initialized when the stepper is loaded

What is the use-case or motivation for changing an existing behavior?

Inactive steps can have logic that fetches data or does some work that isn't relevant to the current step.

Currently there isn't any mention of how to achieve this in the stepper. However it can be done via <ng-container *ngIf="condition">, not sure if there are any side effects.

<mat-horizontal-stepper>
  <mat-step>
    <ng-container *ngIf="condition">
      ...
    </ng-container>
  </mat-step>
</mat-horizontal-stepper>

The expansion panel supports lazy rendering "officially" https://material.angular.io/components/expansion/overview#lazy-rendering

<mat-expansion-panel>
  <mat-expansion-panel-header>
    This is the expansion title
  </mat-expansion-panel-header>

  <ng-template matExpansionPanelContent>
    Some deferred content
  </ng-template>
</mat-expansion-panel>

Why is there a difference? If the <ng-container> is a perfectly fine solution why doesn't the expansion panel do it the same way? Why is there no mention of lazy rendering in the stepper doc?

Which versions of Angular, Material, OS, TypeScript, browsers are affected?

     _                      _                 ____ _     ___
    / \   _ __   __ _ _   _| | __ _ _ __     / ___| |   |_ _|
   / â–³ \ | '_ \ / _` | | | | |/ _` | '__|   | |   | |    | |
  / ___ \| | | | (_| | |_| | | (_| | |      | |___| |___ | |
 /_/   \_\_| |_|\__, |\__,_|_|\__,_|_|       \____|_____|___|
                |___/


Angular CLI: 6.1.2
Node: 9.10.1
OS: win32 x64
Angular: 6.1.0
... animations, common, compiler, compiler-cli, core, forms
... http, language-service, platform-browser
... platform-browser-dynamic, router

Package                           Version
-----------------------------------------------------------
@angular-devkit/architect         0.7.1
@angular-devkit/build-angular     0.7.1
@angular-devkit/build-optimizer   0.7.1
@angular-devkit/build-webpack     0.7.1
@angular-devkit/core              0.7.1
@angular-devkit/schematics        0.7.1
@angular/cdk                      6.4.2
@angular/cli                      6.1.2
@angular/flex-layout              6.0.0-beta.17
@angular/material                 6.4.2
@ngtools/webpack                  6.1.1
@schematics/angular               0.7.2
@schematics/update                0.7.2
rxjs                              6.2.2
typescript                        2.9.2
webpack                           4.9.2
P4 cdstepper feature perf

Most helpful comment

Not sure if I am just stating the obvious, but I was able to implement lazy-loading based on @snebjorn 's approach quite easily:

<mat-vertical-stepper #stepper>
   <mat-step #first_step>
      <ng-container *ngIf="stepper.selected == null || stepper.selected == first_step">
      </ng-container>
   </mat-step>
   <mat-step #second_step>
      <ng-container *ngIf="stepper.selected == second_step">
      </ng-container>
   </mat-step>
   <mat-step #third_step>
      <ng-container *ngIf="stepper.selected == third_step">
      </ng-container>
   </mat-step>
</mat-vertical-stepper>

Note that the condition for the first step needs to include the check for null, too. Otherwise you'll receive an ExpressionChangedAfterItHasBeenCheckedError.

All 13 comments

Yes, I need this as well for a project. I tried a number of approaches but they don't seem to be supported by the current implementation as the compiler throws errors or steps that are added later don't show up.

Here's a nice (related) example of using ng-container *ngTemplateOutlet inside of *ngIf and *ngFor.

It would be really cool if its supported out of the box

Not sure if I am just stating the obvious, but I was able to implement lazy-loading based on @snebjorn 's approach quite easily:

<mat-vertical-stepper #stepper>
   <mat-step #first_step>
      <ng-container *ngIf="stepper.selected == null || stepper.selected == first_step">
      </ng-container>
   </mat-step>
   <mat-step #second_step>
      <ng-container *ngIf="stepper.selected == second_step">
      </ng-container>
   </mat-step>
   <mat-step #third_step>
      <ng-container *ngIf="stepper.selected == third_step">
      </ng-container>
   </mat-step>
</mat-vertical-stepper>

Note that the condition for the first step needs to include the check for null, too. Otherwise you'll receive an ExpressionChangedAfterItHasBeenCheckedError.

Is there any update on when feature will be available?

@matthiaswelz I have a linear form like below. How can i implement your approach in this?

<mat-horizontal-stepper [linear]="true" #stepper [selectedIndex]="0">
  <mat-step [stepControl]="step1.stepFormGroup">
    <ng-template matStepLabel>Step 1</ng-template>
    <app-step-one #step1></app-step-one>
  </mat-step>
  <mat-step [stepControl]="step2.stepFormGroup">
    <ng-template matStepLabel>Step 2</ng-template>
    <app-step-two #step2 ></app-step-two>
  </mat-step>
  <mat-step [stepControl]="step3.stepFormGroup">
    <ng-template matStepLabel>Step 3</ng-template>
    <app-step-three #step3 ></app-step-three>
  </mat-step>
  <mat-step>
    <ng-template matStepLabel>Done</ng-template>
    You are now done.
    <div>
      <button mat-button matStepperPrevious>Back</button>
    </div>
  </mat-step>
</mat-horizontal-stepper>

If i try to add <app-step-one #step1></app-step-one> inside ng-container with *ngIf then i am unable to get [stepControl]="step1.stepFormGroup" from working.

Is there any update?

I tried @matthiaswelz solution, but it still seems to be loading all 3 components. I can verify this by removing the 2nd & 3rd components and watch the load time of the first step go down from 10 seconds to 2 seconds. The components in each of my three steps are not huge, but not trivial either.

@akvaliya A workaround that I haven't tried:

  • Create step1FormGroup, step2FormGroup, and step3FormGroup in the parent component you have here.
  • Pass them down to app-step-one, ... etc.

Yup, not so clean. Also: not sure if a FormGroup must be initialized when constructed or not, but it must, then it's even not-cleaner, as the parent component will now know too much about the children.

You can use use to load content of steps and in routes' file, use lazy loading for loading modules in each step.

template file:

<mat-horizontal-stepper #stepper>
    <mat-step *ngFor="let step of steps; let i=index;">
        <router-outlet *ngIf="stepper.selectedIndex==i"></router-outlet>
    </mat-step> 
</mat-horizontal-stepper>

routes.ts:

const routes: Routes = [
      { path: 'step0', loadChildren: module for component 1},
    ]
 }
];

@matthiaswelz "CdkStep and TemplateRef have no overlap".

Next level of @matthiaswelz's workaround in order to get the collapse animation right: Render the previous step 225ms longer.

<mat-vertical-stepper (selectionChange)="selectionChange($event)">
  <mat-step #step1>
    <ng-container *ngIf="renderStep('default') || renderStep(step1)">
      ...
    </ng-container>
  </mat-step>
  <mat-step #step2>
    <ng-container *ngIf="renderStep(step2)">
      ...
    </ng-container>
  </mat-step>
private _renderStep: CdkStep[] = [];
renderStep(step: CdkStep | 'default'): boolean {
  // workaround for https://github.com/angular/components/issues/12817#issuecomment-457314797
  return step === 'default' ? this._renderStep.length === 0 : this._renderStep.includes(step);
}
selectionChange(event: StepperSelectionEvent) {
  this._renderStep.push(event.selectedStep);
  // see https://github.com/angular/components/blob/master/src/material/stepper/stepper-animations.ts
  setTimeout(() => this._renderStep = [event.selectedStep], 225);
}

It looks like a duplicate of #12339, which has been closed due to the merge of the #15817.

@crisbeto, Btw, this feature will only be available in version 11.3.0, right?

Was this page helpful?
0 / 5 - 0 ratings

Related issues

dzrust picture dzrust  Â·  3Comments

MurhafSousli picture MurhafSousli  Â·  3Comments

julianobrasil picture julianobrasil  Â·  3Comments

alanpurple picture alanpurple  Â·  3Comments

jelbourn picture jelbourn  Â·  3Comments