diff --git a/src/cdk/stepper/stepper.md b/src/cdk/stepper/stepper.md index 6d480d154e6f..2cc43082e459 100644 --- a/src/cdk/stepper/stepper.md +++ b/src/cdk/stepper/stepper.md @@ -45,6 +45,10 @@ There are two button directives to support navigation between different steps: `CdkStepperNext` and `CdkStepperPrevious`. When placed inside of a step, these will automatically add click handlers to advance or rewind the workflow, respectively. +### Resetting a stepper +If you want to reset a stepper to its initial state, you can use the `reset` method. Note that +resetting it will call `reset` on the underlying form control which clears the value. + ### Keyboard interaction - LEFT_ARROW: Focuses the previous step header - RIGHT_ARROW: Focuses the next step header diff --git a/src/cdk/stepper/stepper.ts b/src/cdk/stepper/stepper.ts index 3d9a3f429055..526175a2c88e 100644 --- a/src/cdk/stepper/stepper.ts +++ b/src/cdk/stepper/stepper.ts @@ -123,6 +123,16 @@ export class CdkStep implements OnChanges { this._stepper.selected = this; } + /** Resets the step to its initial state. Note that this includes resetting form data. */ + reset(): void { + this.interacted = false; + this.completed = false; + + if (this.stepControl) { + this.stepControl.reset(); + } + } + ngOnChanges() { // Since basically all inputs of the MatStep get proxied through the view down to the // underlying MatStepHeader, we have to make sure that change detection runs correctly. @@ -208,6 +218,13 @@ export class CdkStepper implements OnDestroy { this.selectedIndex = Math.max(this._selectedIndex - 1, 0); } + /** Resets the stepper to its initial state. Note that this includes clearing form data. */ + reset(): void { + this.selectedIndex = 0; + this._steps.forEach(step => step.reset()); + this._stateChanged(); + } + /** Returns a unique id for each step label element. */ _getStepLabelId(i: number): string { return `cdk-step-label-${this._groupId}-${i}`; diff --git a/src/demo-app/stepper/stepper-demo.html b/src/demo-app/stepper/stepper-demo.html index 0dd4274921ba..244f47772e75 100644 --- a/src/demo-app/stepper/stepper-demo.html +++ b/src/demo-app/stepper/stepper-demo.html @@ -2,7 +2,7 @@

Linear Vertical Stepper Demo using a single form

- + Fill out your name @@ -38,13 +38,14 @@

Linear Vertical Stepper Demo using a single form

Everything seems correct.
+

Linear Horizontal Stepper Demo using a different form for each step

- +
Fill out your name @@ -82,6 +83,7 @@

Linear Horizontal Stepper Demo using a different form for each step

Everything seems correct.
+
diff --git a/src/lib/stepper/stepper.spec.ts b/src/lib/stepper/stepper.spec.ts index 5dd85b3bc260..a533d15336d0 100644 --- a/src/lib/stepper/stepper.spec.ts +++ b/src/lib/stepper/stepper.spec.ts @@ -250,6 +250,10 @@ describe('MatHorizontalStepper', () => { expect(stepper.selectedIndex).toBe(1); }); + + it('should be able to reset the stepper to its initial state', () => { + assertLinearStepperResetable(fixture); + }); }); }); @@ -413,6 +417,10 @@ describe('MatVerticalStepper', () => { it('should be able to move to next step even when invalid if current step is optional', () => { assertOptionalStepValidity(testComponent, fixture); }); + + it('should be able to reset the stepper to its initial state', () => { + assertLinearStepperResetable(fixture); + }); }); }); @@ -850,6 +858,49 @@ function asyncValidator(minLength: number, validationTrigger: Observable): }; } + +/** Asserts that a stepper can be reset. */ +function assertLinearStepperResetable( + fixture: ComponentFixture) { + + const testComponent = fixture.componentInstance; + const stepperComponent = fixture.debugElement.query(By.directive(MatStepper)).componentInstance; + const steps = stepperComponent._steps.toArray(); + + testComponent.oneGroup.get('oneCtrl')!.setValue('value'); + fixture.detectChanges(); + + stepperComponent.next(); + fixture.detectChanges(); + + stepperComponent.next(); + fixture.detectChanges(); + + expect(stepperComponent.selectedIndex).toBe(1); + expect(steps[0].interacted).toBe(true); + expect(steps[0].completed).toBe(true); + expect(testComponent.oneGroup.get('oneCtrl')!.valid).toBe(true); + expect(testComponent.oneGroup.get('oneCtrl')!.value).toBe('value'); + + expect(steps[1].interacted).toBe(true); + expect(steps[1].completed).toBe(false); + expect(testComponent.twoGroup.get('twoCtrl')!.valid).toBe(false); + + stepperComponent.reset(); + fixture.detectChanges(); + + expect(stepperComponent.selectedIndex).toBe(0); + expect(steps[0].interacted).toBe(false); + expect(steps[0].completed).toBe(false); + expect(testComponent.oneGroup.get('oneCtrl')!.valid).toBe(false); + expect(testComponent.oneGroup.get('oneCtrl')!.value).toBeFalsy(); + + expect(steps[1].interacted).toBe(false); + expect(steps[1].completed).toBe(false); + expect(testComponent.twoGroup.get('twoCtrl')!.valid).toBe(false); +} + + @Component({ template: ` diff --git a/src/material-examples/stepper-overview/stepper-overview-example.html b/src/material-examples/stepper-overview/stepper-overview-example.html index 2e4b12c42600..538072ed0372 100644 --- a/src/material-examples/stepper-overview/stepper-overview-example.html +++ b/src/material-examples/stepper-overview/stepper-overview-example.html @@ -1,6 +1,6 @@ - +
Fill out your name @@ -29,6 +29,7 @@ You are now done.
+