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.
This commit is contained in:
Kristiyan Kostadinov
2018-01-23 16:21:52 +01:00
committed by Jeremy Elbourn
parent c8743e70de
commit a6f9fc25d2
5 changed files with 78 additions and 3 deletions
+4
View File
@@ -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
- <kbd>LEFT_ARROW</kbd>: Focuses the previous step header
- <kbd>RIGHT_ARROW</kbd>: Focuses the next step header
+17
View File
@@ -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}`;
+4 -2
View File
@@ -2,7 +2,7 @@
<h3>Linear Vertical Stepper Demo using a single form</h3>
<form [formGroup]="formGroup">
<mat-vertical-stepper formArrayName="formArray" [linear]="!isNonLinear">
<mat-vertical-stepper #linearVerticalStepper="matVerticalStepper" formArrayName="formArray" [linear]="!isNonLinear">
<mat-step formGroupName="0" [stepControl]="formArray?.get([0])">
<ng-template matStepLabel>Fill out your name</ng-template>
<mat-form-field>
@@ -38,13 +38,14 @@
Everything seems correct.
<div>
<button mat-button>Done</button>
<button type="button" mat-button (click)="linearVerticalStepper.reset()">Reset</button>
</div>
</mat-step>
</mat-vertical-stepper>
</form>
<h3>Linear Horizontal Stepper Demo using a different form for each step</h3>
<mat-horizontal-stepper [linear]="!isNonLinear">
<mat-horizontal-stepper #linearHorizontalStepper="matHorizontalStepper" [linear]="!isNonLinear">
<mat-step [stepControl]="nameFormGroup">
<form [formGroup]="nameFormGroup">
<ng-template matStepLabel>Fill out your name</ng-template>
@@ -82,6 +83,7 @@
Everything seems correct.
<div>
<button mat-button>Done</button>
<button type="button" mat-button (click)="linearHorizontalStepper.reset()">Reset</button>
</div>
</form>
</mat-step>
+51
View File
@@ -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<any>):
};
}
/** Asserts that a stepper can be reset. */
function assertLinearStepperResetable(
fixture: ComponentFixture<LinearMatHorizontalStepperApp|LinearMatVerticalStepperApp>) {
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: `
<mat-horizontal-stepper>
@@ -1,6 +1,6 @@
<button mat-raised-button (click)="isLinear = true" id="toggle-linear">Enable linear mode</button>
<mat-horizontal-stepper [linear]="isLinear">
<mat-horizontal-stepper [linear]="isLinear" #stepper="matHorizontalStepper">
<mat-step [stepControl]="firstFormGroup">
<form [formGroup]="firstFormGroup">
<ng-template matStepLabel>Fill out your name</ng-template>
@@ -29,6 +29,7 @@
You are now done.
<div>
<button mat-button matStepperPrevious>Back</button>
<button mat-button (click)="stepper.reset()">Reset</button>
</div>
</mat-step>
</mat-horizontal-stepper>