Skip to content

Commit 52f39bc

Browse files
authored
feat(material/stepper): allow for content to be rendered lazily (#15817)
Adds the `matStepContent` directive that allows consumers to defer rendering the content of a step until it is opened for the first time. Fixes #12339.
1 parent 510672e commit 52f39bc

File tree

12 files changed

+186
-10
lines changed

12 files changed

+186
-10
lines changed

src/components-examples/material/stepper/index.ts

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@ import {StepperOverviewExample} from './stepper-overview/stepper-overview-exampl
1414
import {StepperStatesExample} from './stepper-states/stepper-states-example';
1515
import {StepperVerticalExample} from './stepper-vertical/stepper-vertical-example';
1616
import {StepperHarnessExample} from './stepper-harness/stepper-harness-example';
17+
import {StepperLazyContentExample} from './stepper-lazy-content/stepper-lazy-content-example';
1718

1819
export {
1920
StepperEditableExample,
@@ -24,6 +25,7 @@ export {
2425
StepperOverviewExample,
2526
StepperStatesExample,
2627
StepperVerticalExample,
28+
StepperLazyContentExample,
2729
};
2830

2931
const EXAMPLES = [
@@ -35,6 +37,7 @@ const EXAMPLES = [
3537
StepperOverviewExample,
3638
StepperStatesExample,
3739
StepperVerticalExample,
40+
StepperLazyContentExample,
3841
];
3942

4043
@NgModule({
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
/** No CSS for this example */
Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
<mat-vertical-stepper>
2+
<mat-step>
3+
<ng-template matStepLabel>Step 1</ng-template>
4+
<ng-template matStepContent>
5+
<p>This content was rendered lazily</p>
6+
<button mat-button matStepperNext>Next</button>
7+
</ng-template>
8+
</mat-step>
9+
<mat-step>
10+
<ng-template matStepLabel>Step 2</ng-template>
11+
<ng-template matStepContent>
12+
<p>This content was also rendered lazily</p>
13+
<button mat-button matStepperPrevious>Back</button>
14+
<button mat-button matStepperNext>Next</button>
15+
</ng-template>
16+
</mat-step>
17+
<mat-step>
18+
<ng-template matStepLabel>Step 3</ng-template>
19+
<p>This content was rendered eagerly</p>
20+
<button mat-button matStepperPrevious>Back</button>
21+
</mat-step>
22+
</mat-vertical-stepper>
Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
import {Component} from '@angular/core';
2+
3+
/**
4+
* @title Stepper lazy content rendering
5+
*/
6+
@Component({
7+
selector: 'stepper-lazy-content-example',
8+
templateUrl: 'stepper-lazy-content-example.html',
9+
styleUrls: ['stepper-lazy-content-example.css'],
10+
})
11+
export class StepperLazyContentExample {}

src/material/stepper/public-api.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,3 +14,4 @@ export * from './step-header';
1414
export * from './stepper-intl';
1515
export * from './stepper-animations';
1616
export * from './stepper-icon';
17+
export * from './step-content';
Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
/**
2+
* @license
3+
* Copyright Google LLC All Rights Reserved.
4+
*
5+
* Use of this source code is governed by an MIT-style license that can be
6+
* found in the LICENSE file at https://angular.io/license
7+
*/
8+
9+
import {Directive, TemplateRef} from '@angular/core';
10+
11+
/**
12+
* Content for a `mat-step` that will be rendered lazily.
13+
*/
14+
@Directive({
15+
selector: 'ng-template[matStepContent]'
16+
})
17+
export class MatStepContent {
18+
constructor(public _template: TemplateRef<any>) {}
19+
}

src/material/stepper/step.html

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1 +1,4 @@
1-
<ng-template><ng-content></ng-content></ng-template>
1+
<ng-template>
2+
<ng-content></ng-content>
3+
<ng-template [cdkPortalOutlet]="_portal"></ng-template>
4+
</ng-template>

src/material/stepper/stepper-module.ts

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@ import {MatHorizontalStepper, MatStep, MatStepper, MatVerticalStepper} from './s
1919
import {MatStepperNext, MatStepperPrevious} from './stepper-button';
2020
import {MatStepperIcon} from './stepper-icon';
2121
import {MAT_STEPPER_INTL_PROVIDER} from './stepper-intl';
22+
import {MatStepContent} from './step-content';
2223

2324

2425
@NgModule({
@@ -42,6 +43,7 @@ import {MAT_STEPPER_INTL_PROVIDER} from './stepper-intl';
4243
MatStepperPrevious,
4344
MatStepHeader,
4445
MatStepperIcon,
46+
MatStepContent,
4547
],
4648
declarations: [
4749
MatHorizontalStepper,
@@ -53,6 +55,7 @@ import {MAT_STEPPER_INTL_PROVIDER} from './stepper-intl';
5355
MatStepperPrevious,
5456
MatStepHeader,
5557
MatStepperIcon,
58+
MatStepContent,
5659
],
5760
providers: [MAT_STEPPER_INTL_PROVIDER, ErrorStateMatcher],
5861
})

src/material/stepper/stepper.md

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -180,6 +180,13 @@ will not affect steppers marked as `linear`.
180180

181181
<!-- example(stepper-errors) -->
182182

183+
### Lazy rendering
184+
By default, the stepper will render all of it's content when it's initialized. If you have some
185+
content that you want to want to defer until the particular step is opened, you can put it inside
186+
an `ng-template` with the `matStepContent` attribute.
187+
188+
<!-- example(stepper-lazy-content) -->
189+
183190
### Keyboard interaction
184191
- <kbd>LEFT_ARROW</kbd>: Focuses the previous step header
185192
- <kbd>RIGHT_ARROW</kbd>: Focuses the next step header

src/material/stepper/stepper.spec.ts

Lines changed: 61 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1302,6 +1302,44 @@ describe('MatStepper', () => {
13021302
expect(stepper.selectedIndex).toBe(1);
13031303
expect(stepper.selected).toBeTruthy();
13041304
});
1305+
1306+
describe('stepper with lazy content', () => {
1307+
it('should render the content of the selected step on init', () => {
1308+
const fixture = createComponent(StepperWithLazyContent);
1309+
const element = fixture.nativeElement;
1310+
fixture.componentInstance.selectedIndex = 1;
1311+
fixture.detectChanges();
1312+
1313+
expect(element.textContent).not.toContain('Step 1 content');
1314+
expect(element.textContent).toContain('Step 2 content');
1315+
expect(element.textContent).not.toContain('Step 3 content');
1316+
});
1317+
1318+
it('should render the content of steps when the user navigates to them', () => {
1319+
const fixture = createComponent(StepperWithLazyContent);
1320+
const element = fixture.nativeElement;
1321+
fixture.componentInstance.selectedIndex = 0;
1322+
fixture.detectChanges();
1323+
1324+
expect(element.textContent).toContain('Step 1 content');
1325+
expect(element.textContent).not.toContain('Step 2 content');
1326+
expect(element.textContent).not.toContain('Step 3 content');
1327+
1328+
fixture.componentInstance.selectedIndex = 1;
1329+
fixture.detectChanges();
1330+
1331+
expect(element.textContent).toContain('Step 1 content');
1332+
expect(element.textContent).toContain('Step 2 content');
1333+
expect(element.textContent).not.toContain('Step 3 content');
1334+
1335+
fixture.componentInstance.selectedIndex = 2;
1336+
fixture.detectChanges();
1337+
1338+
expect(element.textContent).toContain('Step 1 content');
1339+
expect(element.textContent).toContain('Step 2 content');
1340+
expect(element.textContent).toContain('Step 3 content');
1341+
});
1342+
});
13051343
});
13061344

13071345
/** Asserts that keyboard interaction works correctly. */
@@ -1826,3 +1864,26 @@ class NestedSteppers {
18261864
class StepperWithStaticOutOfBoundsIndex {
18271865
@ViewChild(MatStepper) stepper: MatStepper;
18281866
}
1867+
1868+
1869+
@Component({
1870+
template: `
1871+
<mat-vertical-stepper [selectedIndex]="selectedIndex">
1872+
<mat-step>
1873+
<ng-template matStepLabel>Step 1</ng-template>
1874+
<ng-template matStepContent>Step 1 content</ng-template>
1875+
</mat-step>
1876+
<mat-step>
1877+
<ng-template matStepLabel>Step 2</ng-template>
1878+
<ng-template matStepContent>Step 2 content</ng-template>
1879+
</mat-step>
1880+
<mat-step>
1881+
<ng-template matStepLabel>Step 3</ng-template>
1882+
<ng-template matStepContent>Step 3 content</ng-template>
1883+
</mat-step>
1884+
</mat-vertical-stepper>
1885+
`
1886+
})
1887+
class StepperWithLazyContent {
1888+
selectedIndex = 0;
1889+
}

0 commit comments

Comments
 (0)