Skip to content

Commit dfc5edd

Browse files
committed
feat(stepper): add the ability to reset a stepper
Allows for the user to reset a stepper to its initial state by calling the `reset` method. Relates to #7700.
1 parent 5210b3e commit dfc5edd

File tree

5 files changed

+79
-4
lines changed

5 files changed

+79
-4
lines changed

src/cdk/stepper/stepper.md

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,10 @@ There are two button directives to support navigation between different steps:
4141
`CdkStepperNext` and `CdkStepperPrevious`. When placed inside of a step, these will automatically
4242
add click handlers to advance or rewind the workflow, respectively.
4343

44+
### Resetting a stepper
45+
If you want to reset a stepper to its initial state, you can use the `reset` method. Note that
46+
resetting it will call `reset` on the underlying form control which clears the value.
47+
4448
### Keyboard interaction
4549
- <kbd>LEFT_ARROW</kbd>: Focuses the previous step header
4650
- <kbd>RIGHT_ARROW</kbd>: Focuses the next step header
@@ -56,4 +60,4 @@ is given `role="tab"`, and the content that can be expanded upon selection is gi
5660
step content is automatically set based on step selection change.
5761

5862
The stepper and each step should be given a meaningful label via `aria-label` or `aria-labelledby`.
59-
63+

src/cdk/stepper/stepper.ts

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -120,6 +120,16 @@ export class CdkStep implements OnChanges {
120120
this._stepper.selected = this;
121121
}
122122

123+
/** Resets the step to its initial state. Note that this includes resetting form data. */
124+
reset(): void {
125+
this.interacted = false;
126+
this.completed = false;
127+
128+
if (this.stepControl) {
129+
this.stepControl.reset();
130+
}
131+
}
132+
123133
ngOnChanges() {
124134
// Since basically all inputs of the MdStep get proxied through the view down to the
125135
// underlying MdStepHeader, we have to make sure that change detection runs correctly.
@@ -203,6 +213,13 @@ export class CdkStepper implements OnDestroy {
203213
this.selectedIndex = Math.max(this._selectedIndex - 1, 0);
204214
}
205215

216+
/** Resets the stepper to its initial state. Note that this includes clearing form data. */
217+
reset(): void {
218+
this.selectedIndex = 0;
219+
this._steps.forEach(step => step.reset());
220+
this._stateChanged();
221+
}
222+
206223
/** Returns a unique id for each step label element. */
207224
_getStepLabelId(i: number): string {
208225
return `cdk-step-label-${this._groupId}-${i}`;

src/demo-app/stepper/stepper-demo.html

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22

33
<h3>Linear Vertical Stepper Demo using a single form</h3>
44
<form [formGroup]="formGroup">
5-
<mat-vertical-stepper formArrayName="formArray" [linear]="!isNonLinear">
5+
<mat-vertical-stepper #linearVerticalStepper="matVerticalStepper" formArrayName="formArray" [linear]="!isNonLinear">
66
<mat-step formGroupName="0" [stepControl]="formArray?.get([0])">
77
<ng-template matStepLabel>Fill out your name</ng-template>
88
<mat-form-field>
@@ -38,13 +38,14 @@ <h3>Linear Vertical Stepper Demo using a single form</h3>
3838
Everything seems correct.
3939
<div>
4040
<button mat-button>Done</button>
41+
<button type="button" mat-button (click)="linearVerticalStepper.reset()">Reset</button>
4142
</div>
4243
</mat-step>
4344
</mat-vertical-stepper>
4445
</form>
4546

4647
<h3>Linear Horizontal Stepper Demo using a different form for each step</h3>
47-
<mat-horizontal-stepper [linear]="!isNonLinear">
48+
<mat-horizontal-stepper #linearHorizontalStepper="matHorizontalStepper" [linear]="!isNonLinear">
4849
<mat-step [stepControl]="nameFormGroup">
4950
<form [formGroup]="nameFormGroup">
5051
<ng-template matStepLabel>Fill out your name</ng-template>
@@ -82,6 +83,7 @@ <h3>Linear Horizontal Stepper Demo using a different form for each step</h3>
8283
Everything seems correct.
8384
<div>
8485
<button mat-button>Done</button>
86+
<button type="button" mat-button (click)="linearHorizontalStepper.reset()">Reset</button>
8587
</div>
8688
</form>
8789
</mat-step>

src/lib/stepper/stepper.spec.ts

Lines changed: 51 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -199,6 +199,10 @@ describe('MatHorizontalStepper', () => {
199199
let stepHeaders = debugElement.queryAll(By.css('.mat-horizontal-stepper-header'));
200200
assertSelectionChangeOnHeaderClick(preselectedFixture, stepHeaders);
201201
});
202+
203+
it('should be able to reset the stepper to its initial state', () => {
204+
assertLinearStepperResetable(fixture);
205+
});
202206
});
203207
});
204208

@@ -357,6 +361,10 @@ describe('MatVerticalStepper', () => {
357361
it('should be able to move to next step even when invalid if current step is optional', () => {
358362
assertOptionalStepValidity(testComponent, fixture);
359363
});
364+
365+
it('should be able to reset the stepper to its initial state', () => {
366+
assertLinearStepperResetable(fixture);
367+
});
360368
});
361369
});
362370

@@ -792,6 +800,49 @@ function asyncValidator(minLength: number, validationTrigger: Observable<any>):
792800
};
793801
}
794802

803+
804+
/** Asserts that a stepper can be reset. */
805+
function assertLinearStepperResetable(
806+
fixture: ComponentFixture<LinearMatHorizontalStepperApp|LinearMatVerticalStepperApp>) {
807+
808+
const testComponent = fixture.componentInstance;
809+
const stepperComponent = fixture.debugElement.query(By.directive(MatStepper)).componentInstance;
810+
const steps = stepperComponent._steps.toArray();
811+
812+
testComponent.oneGroup.get('oneCtrl')!.setValue('value');
813+
fixture.detectChanges();
814+
815+
stepperComponent.next();
816+
fixture.detectChanges();
817+
818+
stepperComponent.next();
819+
fixture.detectChanges();
820+
821+
expect(stepperComponent.selectedIndex).toBe(1);
822+
expect(steps[0].interacted).toBe(true);
823+
expect(steps[0].completed).toBe(true);
824+
expect(testComponent.oneGroup.get('oneCtrl')!.valid).toBe(true);
825+
expect(testComponent.oneGroup.get('oneCtrl')!.value).toBe('value');
826+
827+
expect(steps[1].interacted).toBe(true);
828+
expect(steps[1].completed).toBe(false);
829+
expect(testComponent.twoGroup.get('twoCtrl')!.valid).toBe(false);
830+
831+
stepperComponent.reset();
832+
fixture.detectChanges();
833+
834+
expect(stepperComponent.selectedIndex).toBe(0);
835+
expect(steps[0].interacted).toBe(false);
836+
expect(steps[0].completed).toBe(false);
837+
expect(testComponent.oneGroup.get('oneCtrl')!.valid).toBe(false);
838+
expect(testComponent.oneGroup.get('oneCtrl')!.value).toBeFalsy();
839+
840+
expect(steps[1].interacted).toBe(false);
841+
expect(steps[1].completed).toBe(false);
842+
expect(testComponent.twoGroup.get('twoCtrl')!.valid).toBe(false);
843+
}
844+
845+
795846
@Component({
796847
template: `
797848
<mat-horizontal-stepper>

src/material-examples/stepper-overview/stepper-overview-example.html

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
<button mat-raised-button (click)="isLinear = true" id="toggle-linear">Enable linear mode</button>
22

3-
<mat-horizontal-stepper [linear]="isLinear">
3+
<mat-horizontal-stepper [linear]="isLinear" #stepper="matHorizontalStepper">
44
<mat-step [stepControl]="firstFormGroup">
55
<form [formGroup]="firstFormGroup">
66
<ng-template matStepLabel>Fill out your name</ng-template>
@@ -29,6 +29,7 @@
2929
You are now done.
3030
<div>
3131
<button mat-button matStepperPrevious>Back</button>
32+
<button mat-button (click)="matHorizontalStepper.reset()">Reset</button>
3233
</div>
3334
</mat-step>
3435
</mat-horizontal-stepper>

0 commit comments

Comments
 (0)