Skip to content

feat(stepper): add the ability to reset a stepper #8623

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 1 commit into from
Jan 23, 2018
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions src/cdk/stepper/stepper.md
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
17 changes: 17 additions & 0 deletions src/cdk/stepper/stepper.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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();
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Note: I'm open to having this reset only the dirty and touched states. I went with reset, because leaving behind the value didn't seem like a "true" reset.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This seems like the most common use case to me. If we get lot of requests for reset all but the value we could create a new method: {reset,clear}{Status,State}

}
}

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.
Expand Down Expand Up @@ -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}`;
Expand Down
6 changes: 4 additions & 2 deletions src/demo-app/stepper/stepper-demo.html
Original file line number Diff line number Diff line change
Expand Up @@ -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>
Expand Down Expand Up @@ -38,13 +38,14 @@ <h3>Linear Vertical Stepper Demo using a single form</h3>
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>
Expand Down Expand Up @@ -82,6 +83,7 @@ <h3>Linear Horizontal Stepper Demo using a different form for each step</h3>
Everything seems correct.
<div>
<button mat-button>Done</button>
<button type="button" mat-button (click)="linearHorizontalStepper.reset()">Reset</button>
</div>
</form>
</mat-step>
Expand Down
51 changes: 51 additions & 0 deletions src/lib/stepper/stepper.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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);
});
});
});

Expand Down Expand Up @@ -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);
});
});
});

Expand Down Expand Up @@ -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>
Expand Down
Original file line number Diff line number Diff line change
@@ -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>
Expand Down Expand Up @@ -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>