From a6f9fc25d24717343db0267f50d319eb06260629 Mon Sep 17 00:00:00 2001 From: Kristiyan Kostadinov Date: Tue, 23 Jan 2018 16:21:52 +0100 Subject: [PATCH] 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. --- src/cdk/stepper/stepper.md | 4 ++ src/cdk/stepper/stepper.ts | 17 +++++++ src/demo-app/stepper/stepper-demo.html | 6 ++- src/lib/stepper/stepper.spec.ts | 51 +++++++++++++++++++ .../stepper-overview-example.html | 3 +- 5 files changed, 78 insertions(+), 3 deletions(-) diff --git a/src/cdk/stepper/stepper.md b/src/cdk/stepper/stepper.md index 6d480d154e6..2cc43082e45 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 f866477ae00..5317ea04de1 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. @@ -209,6 +219,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 0dd4274921b..244f47772e7 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 @@ Everything seems correct.
+

Linear Horizontal Stepper Demo using a different form for each step

- +
Fill out your name @@ -82,6 +83,7 @@ Everything seems correct.
+
diff --git a/src/lib/stepper/stepper.spec.ts b/src/lib/stepper/stepper.spec.ts index 5dd85b3bc26..a533d15336d 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 2e4b12c4260..538072ed037 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.
+