Skip to content

Commit a6f9fc2

Browse files
crisbetojelbourn
authored andcommitted
feat(stepper): add the ability to reset a stepper (#8623)
Allows for the user to reset a stepper to its initial state by calling the `reset` method. Relates to #7700.
1 parent c8743e7 commit a6f9fc2

File tree

5 files changed

+78
-3
lines changed

5 files changed

+78
-3
lines changed

src/cdk/stepper/stepper.md

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -45,6 +45,10 @@ There are two button directives to support navigation between different steps:
4545
`CdkStepperNext` and `CdkStepperPrevious`. When placed inside of a step, these will automatically
4646
add click handlers to advance or rewind the workflow, respectively.
4747

48+
### Resetting a stepper
49+
If you want to reset a stepper to its initial state, you can use the `reset` method. Note that
50+
resetting it will call `reset` on the underlying form control which clears the value.
51+
4852
### Keyboard interaction
4953
- <kbd>LEFT_ARROW</kbd>: Focuses the previous step header
5054
- <kbd>RIGHT_ARROW</kbd>: Focuses the next step header

src/cdk/stepper/stepper.ts

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

126+
/** Resets the step to its initial state. Note that this includes resetting form data. */
127+
reset(): void {
128+
this.interacted = false;
129+
this.completed = false;
130+
131+
if (this.stepControl) {
132+
this.stepControl.reset();
133+
}
134+
}
135+
126136
ngOnChanges() {
127137
// Since basically all inputs of the MatStep get proxied through the view down to the
128138
// underlying MatStepHeader, we have to make sure that change detection runs correctly.
@@ -209,6 +219,13 @@ export class CdkStepper implements OnDestroy {
209219
this.selectedIndex = Math.max(this._selectedIndex - 1, 0);
210220
}
211221

222+
/** Resets the stepper to its initial state. Note that this includes clearing form data. */
223+
reset(): void {
224+
this.selectedIndex = 0;
225+
this._steps.forEach(step => step.reset());
226+
this._stateChanged();
227+
}
228+
212229
/** Returns a unique id for each step label element. */
213230
_getStepLabelId(i: number): string {
214231
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
@@ -250,6 +250,10 @@ describe('MatHorizontalStepper', () => {
250250

251251
expect(stepper.selectedIndex).toBe(1);
252252
});
253+
254+
it('should be able to reset the stepper to its initial state', () => {
255+
assertLinearStepperResetable(fixture);
256+
});
253257
});
254258
});
255259

@@ -413,6 +417,10 @@ describe('MatVerticalStepper', () => {
413417
it('should be able to move to next step even when invalid if current step is optional', () => {
414418
assertOptionalStepValidity(testComponent, fixture);
415419
});
420+
421+
it('should be able to reset the stepper to its initial state', () => {
422+
assertLinearStepperResetable(fixture);
423+
});
416424
});
417425
});
418426

@@ -850,6 +858,49 @@ function asyncValidator(minLength: number, validationTrigger: Observable<any>):
850858
};
851859
}
852860

861+
862+
/** Asserts that a stepper can be reset. */
863+
function assertLinearStepperResetable(
864+
fixture: ComponentFixture<LinearMatHorizontalStepperApp|LinearMatVerticalStepperApp>) {
865+
866+
const testComponent = fixture.componentInstance;
867+
const stepperComponent = fixture.debugElement.query(By.directive(MatStepper)).componentInstance;
868+
const steps = stepperComponent._steps.toArray();
869+
870+
testComponent.oneGroup.get('oneCtrl')!.setValue('value');
871+
fixture.detectChanges();
872+
873+
stepperComponent.next();
874+
fixture.detectChanges();
875+
876+
stepperComponent.next();
877+
fixture.detectChanges();
878+
879+
expect(stepperComponent.selectedIndex).toBe(1);
880+
expect(steps[0].interacted).toBe(true);
881+
expect(steps[0].completed).toBe(true);
882+
expect(testComponent.oneGroup.get('oneCtrl')!.valid).toBe(true);
883+
expect(testComponent.oneGroup.get('oneCtrl')!.value).toBe('value');
884+
885+
expect(steps[1].interacted).toBe(true);
886+
expect(steps[1].completed).toBe(false);
887+
expect(testComponent.twoGroup.get('twoCtrl')!.valid).toBe(false);
888+
889+
stepperComponent.reset();
890+
fixture.detectChanges();
891+
892+
expect(stepperComponent.selectedIndex).toBe(0);
893+
expect(steps[0].interacted).toBe(false);
894+
expect(steps[0].completed).toBe(false);
895+
expect(testComponent.oneGroup.get('oneCtrl')!.valid).toBe(false);
896+
expect(testComponent.oneGroup.get('oneCtrl')!.value).toBeFalsy();
897+
898+
expect(steps[1].interacted).toBe(false);
899+
expect(steps[1].completed).toBe(false);
900+
expect(testComponent.twoGroup.get('twoCtrl')!.valid).toBe(false);
901+
}
902+
903+
853904
@Component({
854905
template: `
855906
<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)="stepper.reset()">Reset</button>
3233
</div>
3334
</mat-step>
3435
</mat-horizontal-stepper>

0 commit comments

Comments
 (0)