Skip to content

Commit 946fd84

Browse files
crisbetojosephperrott
authored andcommitted
feat(stepper): allow number icon to be customized and expose template context variables (#10516)
* Allows for the step number icon to be overwritten using `matStepperIcon="number"`. * Exposes the `index`, `active` and `optional` template variables to `matStepperIcon`. Fixes #10513.
1 parent b8bb62f commit 946fd84

File tree

6 files changed

+78
-18
lines changed

6 files changed

+78
-18
lines changed

src/lib/stepper/step-header.html

Lines changed: 15 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -3,15 +3,27 @@
33
[class.mat-step-icon-not-touched]="state == 'number' && !selected"
44
[ngSwitch]="state">
55

6-
<span *ngSwitchCase="'number'">{{index + 1}}</span>
6+
<ng-container *ngSwitchCase="'number'" [ngSwitch]="!!(iconOverrides && iconOverrides.number)">
7+
<ng-container
8+
*ngSwitchCase="true"
9+
[ngTemplateOutlet]="iconOverrides.number"
10+
[ngTemplateOutletContext]="_getIconContext()"></ng-container>
11+
<span *ngSwitchDefault>{{index + 1}}</span>
12+
</ng-container>
713

814
<ng-container *ngSwitchCase="'edit'" [ngSwitch]="!!(iconOverrides && iconOverrides.edit)">
9-
<ng-container *ngSwitchCase="true" [ngTemplateOutlet]="iconOverrides.edit"></ng-container>
15+
<ng-container
16+
*ngSwitchCase="true"
17+
[ngTemplateOutlet]="iconOverrides.edit"
18+
[ngTemplateOutletContext]="_getIconContext()"></ng-container>
1019
<mat-icon *ngSwitchDefault>create</mat-icon>
1120
</ng-container>
1221

1322
<ng-container *ngSwitchCase="'done'" [ngSwitch]="!!(iconOverrides && iconOverrides.done)">
14-
<ng-container *ngSwitchCase="true" [ngTemplateOutlet]="iconOverrides.done"></ng-container>
23+
<ng-container
24+
*ngSwitchCase="true"
25+
[ngTemplateOutlet]="iconOverrides.done"
26+
[ngTemplateOutletContext]="_getIconContext()"></ng-container>
1527
<mat-icon *ngSwitchDefault>done</mat-icon>
1628
</ng-container>
1729
</div>

src/lib/stepper/step-header.ts

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@ import {
2020
import {Subscription} from 'rxjs';
2121
import {MatStepLabel} from './step-label';
2222
import {MatStepperIntl} from './stepper-intl';
23+
import {MatStepperIconContext} from './stepper-icon';
2324

2425

2526
@Component({
@@ -44,7 +45,7 @@ export class MatStepHeader implements OnDestroy {
4445
@Input() label: MatStepLabel | string;
4546

4647
/** Overrides for the header icons, passed in via the stepper. */
47-
@Input() iconOverrides: {[key: string]: TemplateRef<any>};
48+
@Input() iconOverrides: {[key: string]: TemplateRef<MatStepperIconContext>};
4849

4950
/** Index of the given step. */
5051
@Input() index: number;
@@ -87,6 +88,15 @@ export class MatStepHeader implements OnDestroy {
8788
return this._element.nativeElement;
8889
}
8990

91+
/** Template context variables that are exposed to the `matStepperIcon` instances. */
92+
_getIconContext(): MatStepperIconContext {
93+
return {
94+
index: this.index,
95+
active: this.active,
96+
optional: this.optional
97+
};
98+
}
99+
90100
focus() {
91101
this._getHostElement().focus();
92102
}

src/lib/stepper/stepper-icon.ts

Lines changed: 12 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,16 @@
88

99
import {Directive, Input, TemplateRef} from '@angular/core';
1010

11+
/** Template context available to an attached `matStepperIcon`. */
12+
export interface MatStepperIconContext {
13+
/** Index of the step. */
14+
index: number;
15+
/** Whether the step is currently active. */
16+
active: boolean;
17+
/** Whether the step is optional. */
18+
optional: boolean;
19+
}
20+
1121
/**
1222
* Template to be used to override the icons inside the step header.
1323
*/
@@ -16,7 +26,7 @@ import {Directive, Input, TemplateRef} from '@angular/core';
1626
})
1727
export class MatStepperIcon {
1828
/** Name of the icon to be overridden. */
19-
@Input('matStepperIcon') name: 'edit' | 'done';
29+
@Input('matStepperIcon') name: 'edit' | 'done' | 'number';
2030

21-
constructor(public templateRef: TemplateRef<any>) { }
31+
constructor(public templateRef: TemplateRef<MatStepperIconContext>) {}
2232
}

src/lib/stepper/stepper.md

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -124,7 +124,8 @@ this default `completed` behavior by setting the `completed` attribute as needed
124124
#### Overriding icons
125125
By default, the step headers will use the `create` and `done` icons from the Material design icon
126126
set via `<mat-icon>` elements. If you want to provide a different set of icons, you can do so
127-
by placing a `matStepperIcon` for each of the icons that you want to override:
127+
by placing a `matStepperIcon` for each of the icons that you want to override. The `index`,
128+
`active`, and `optional` values of the individual steps are available through template variables:
128129

129130
```html
130131
<mat-vertical-stepper>
@@ -136,6 +137,11 @@ by placing a `matStepperIcon` for each of the icons that you want to override:
136137
<mat-icon>done_all</mat-icon>
137138
</ng-template>
138139

140+
<!-- Custom icon with a context variable. -->
141+
<ng-template matStepperIcon="number" let-index="index">
142+
{{index + 10}}
143+
</ng-template>
144+
139145
<!-- Stepper steps go here -->
140146
</mat-vertical-stepper>
141147
```

src/lib/stepper/stepper.spec.ts

Lines changed: 25 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -354,6 +354,13 @@ describe('MatStepper', () => {
354354

355355
expect(header.textContent).toContain('Custom done');
356356
});
357+
358+
it('should allow for the `number` icon to be overridden with context', () => {
359+
const stepperDebugElement = fixture.debugElement.query(By.directive(MatStepper));
360+
const headers = stepperDebugElement.nativeElement.querySelectorAll('mat-step-header');
361+
362+
expect(headers[2].textContent).toContain('III');
363+
});
357364
});
358365

359366
describe('RTL', () => {
@@ -1025,14 +1032,31 @@ class SimpleStepperWithStepControlAndCompletedBinding {
10251032
<mat-horizontal-stepper>
10261033
<ng-template matStepperIcon="edit">Custom edit</ng-template>
10271034
<ng-template matStepperIcon="done">Custom done</ng-template>
1035+
<ng-template matStepperIcon="number" let-index="index">
1036+
{{getRomanNumeral(index + 1)}}
1037+
</ng-template>
10281038
10291039
<mat-step>Content 1</mat-step>
10301040
<mat-step>Content 2</mat-step>
10311041
<mat-step>Content 3</mat-step>
10321042
</mat-horizontal-stepper>
10331043
`
10341044
})
1035-
class IconOverridesStepper {}
1045+
class IconOverridesStepper {
1046+
getRomanNumeral(value: number) {
1047+
return {
1048+
1: 'I',
1049+
2: 'II',
1050+
3: 'III',
1051+
4: 'IV',
1052+
5: 'V',
1053+
6: 'VI',
1054+
7: 'VII',
1055+
8: 'VIII',
1056+
9: 'IX'
1057+
}[value];
1058+
}
1059+
}
10361060

10371061
@Component({
10381062
template: `

src/lib/stepper/stepper.ts

Lines changed: 8 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -31,7 +31,7 @@ import {MatStepHeader} from './step-header';
3131
import {MatStepLabel} from './step-label';
3232
import {takeUntil} from 'rxjs/operators';
3333
import {matStepperAnimations} from './stepper-animations';
34-
import {MatStepperIcon} from './stepper-icon';
34+
import {MatStepperIcon, MatStepperIconContext} from './stepper-icon';
3535

3636
/** Workaround for https://github.com/angular/angular/issues/17849 */
3737
export const _MatStep = CdkStep;
@@ -83,20 +83,18 @@ export class MatStepper extends CdkStepper implements AfterContentInit {
8383
@ContentChildren(MatStepperIcon) _icons: QueryList<MatStepperIcon>;
8484

8585
/** Consumer-specified template-refs to be used to override the header icons. */
86-
_iconOverrides: {[key: string]: TemplateRef<any>} = {};
86+
_iconOverrides: {[key: string]: TemplateRef<MatStepperIconContext>} = {};
8787

8888
ngAfterContentInit() {
8989
const icons = this._icons.toArray();
90-
const editOverride = icons.find(icon => icon.name === 'edit');
91-
const doneOverride = icons.find(icon => icon.name === 'done');
9290

93-
if (editOverride) {
94-
this._iconOverrides.edit = editOverride.templateRef;
95-
}
91+
['edit', 'done', 'number'].forEach(name => {
92+
const override = icons.find(icon => icon.name === name);
9693

97-
if (doneOverride) {
98-
this._iconOverrides.done = doneOverride.templateRef;
99-
}
94+
if (override) {
95+
this._iconOverrides[name] = override.templateRef;
96+
}
97+
});
10098

10199
// Mark the component for change detection whenever the content children query changes
102100
this._steps.changes.pipe(takeUntil(this._destroyed)).subscribe(() => this._stateChanged());

0 commit comments

Comments
 (0)