Skip to content

Commit ccbb85e

Browse files
committed
Add form control - consider each step as its own form group
1 parent 9c41f1d commit ccbb85e

File tree

7 files changed

+123
-6
lines changed

7 files changed

+123
-6
lines changed

src/cdk/stepper/stepper.ts

Lines changed: 19 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@ import {
2424
} from '@angular/core';
2525
import {LEFT_ARROW, RIGHT_ARROW, ENTER, SPACE} from '@angular/cdk/keyboard';
2626
import {CdkStepLabel} from './step-label';
27+
import {coerceBooleanProperty} from '@angular/cdk/coercion';
2728

2829
/** Used to generate unique ID for each stepper component. */
2930
let nextId = 0;
@@ -54,6 +55,21 @@ export class CdkStep {
5455
/** Template for step content. */
5556
@ViewChild(TemplateRef) content: TemplateRef<any>;
5657

58+
/** Whether step is disabled or not. */
59+
@Input()
60+
get disabled() { return this._disabled; }
61+
set disabled(value: any) {
62+
this._disabled = coerceBooleanProperty(value);
63+
}
64+
private _disabled = false;
65+
66+
/** Whether the user has interacted with step or not. */
67+
get interacted() { return this._interacted; }
68+
set interacted(value: any) {
69+
this._interacted = coerceBooleanProperty(value);
70+
}
71+
private _interacted = false;
72+
5773
/** Label of the step. */
5874
@Input()
5975
label: string;
@@ -84,7 +100,8 @@ export class CdkStepper {
84100
@Input()
85101
get selectedIndex() { return this._selectedIndex; }
86102
set selectedIndex(index: number) {
87-
if (this._selectedIndex != index) {
103+
this._steps.toArray()[this._selectedIndex].interacted = true;
104+
if (this._selectedIndex != index && !this._steps.toArray()[index].disabled) {
88105
this._emitStepperSelectionEvent(index);
89106
this._focusStep(this._selectedIndex);
90107
}
@@ -153,7 +170,7 @@ export class CdkStepper {
153170
break;
154171
case SPACE:
155172
case ENTER:
156-
this._emitStepperSelectionEvent(this._focusIndex);
173+
this.selectedIndex = this._focusIndex;
157174
break;
158175
default:
159176
// Return to avoid calling preventDefault on keys that are not explicitly handled.

src/cdk/stepper/tsconfig-build.json

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,8 @@
44
"outDir": "../../../dist/packages/cdk",
55
"baseUrl": ".",
66
"paths": {
7-
"@angular/cdk/keyboard": ["../../../dist/packages/cdk/keyboard/public_api"]
7+
"@angular/cdk/keyboard": ["../../../dist/packages/cdk/keyboard/public_api"],
8+
"@angular/cdk/coercion": ["../../../dist/packages/cdk/coercion/public_api"]
89
}
910
},
1011
"files": [

src/demo-app/stepper/stepper-demo.html

Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,49 @@
1+
<h2>Linear Vertical Stepper Demo</h2>
2+
<md-vertical-stepper>
3+
<md-step>
4+
<form [formGroup]="nameFormGroup" novalidate>
5+
<ng-template mdStepLabel>Fill out your name</ng-template>
6+
<md-input-container>
7+
<input mdInput placeholder="First Name" formControlName="firstNameFormCtrl" required>
8+
<md-error>This field is required</md-error>
9+
</md-input-container>
10+
<md-input-container>
11+
<input mdInput placeholder="Last Name" formControlName="lastNameFormCtrl" required>
12+
<md-error>This field is required</md-error>
13+
</md-input-container>
14+
<div>
15+
<button md-button mdStepperNext>Next</button>
16+
</div>
17+
</form>
18+
</md-step>
19+
20+
<md-step [disabled]="!nameFormGroup.valid">
21+
<form [formGroup]="phoneFormGroup" novalidate>
22+
<ng-template mdStepLabel>
23+
<div>Fill out your phone number</div>
24+
</ng-template>
25+
<md-input-container>
26+
<input mdInput placeholder="Phone number" formControlName="phoneFormCtrl" required>
27+
<md-error>This field is required</md-error>
28+
</md-input-container>
29+
<div>
30+
<button md-button mdStepperPrevious>Back</button>
31+
<button md-button mdStepperNext>Next</button>
32+
</div>
33+
</form>
34+
</md-step>
35+
36+
<md-step [disabled]="!phoneFormGroup.valid">
37+
<form>
38+
<ng-template mdStepLabel>Confirm your information</ng-template>
39+
Everything seems correct.
40+
<div>
41+
<button md-button>Done</button>
42+
</div>
43+
</form>
44+
</md-step>
45+
</md-vertical-stepper>
46+
147
<h2>Horizontal Stepper Demo</h2>
248
<md-horizontal-stepper>
349
<md-step *ngFor="let step of steps" [label]="step.label">

src/demo-app/stepper/stepper-demo.ts

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
import {Component} from '@angular/core';
2+
import {FormControl, FormGroup, Validators} from '@angular/forms';
23

34
@Component({
45
moduleId: module.id,
@@ -7,10 +8,24 @@ import {Component} from '@angular/core';
78
styleUrls: ['stepper-demo.scss'],
89
})
910
export class StepperDemo {
11+
nameFormGroup: FormGroup;
12+
phoneFormGroup: FormGroup;
13+
1014
steps = [
1115
{label: 'Confirm your name', content: 'Last name, First name.'},
1216
{label: 'Confirm your contact information', content: '123-456-7890'},
1317
{label: 'Confirm your address', content: '1600 Amphitheater Pkwy MTV'},
1418
{label: 'You are now done', content: 'Finished!'}
1519
];
20+
21+
ngOnInit() {
22+
this.nameFormGroup = new FormGroup({
23+
firstNameFormCtrl: new FormControl('', Validators.required),
24+
lastNameFormCtrl: new FormControl('', Validators.required)
25+
});
26+
27+
this.phoneFormGroup = new FormGroup({
28+
phoneFormCtrl: new FormControl('', Validators.required)
29+
});
30+
}
1631
}

src/lib/stepper/stepper.ts

Lines changed: 27 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,22 +15,48 @@ import {
1515
// considers such imports as unused (https://github.com/Microsoft/TypeScript/issues/14953)
1616
// tslint:disable-next-line:no-unused-variable
1717
ElementRef,
18+
Inject,
19+
Optional,
1820
QueryList,
21+
SkipSelf,
1922
ViewChildren
2023
}from '@angular/core';
2124
import {MdStepLabel} from './step-label';
25+
import {
26+
defaultErrorStateMatcher,
27+
ErrorOptions,
28+
MD_ERROR_GLOBAL_OPTIONS,
29+
ErrorStateMatcher
30+
} from '../core/error/error-options';
31+
import {FormControl, FormGroupDirective, NgForm} from '@angular/forms';
2232

2333
@Component({
2434
moduleId: module.id,
2535
selector: 'md-step, mat-step',
2636
templateUrl: 'step.html',
37+
providers: [{provide: MD_ERROR_GLOBAL_OPTIONS, useExisting: MdStep}]
2738
})
2839
export class MdStep extends CdkStep {
2940
/** Content for step label given by <ng-template matStepLabel> or <ng-template mdStepLabel>. */
3041
@ContentChild(MdStepLabel) stepLabel: MdStepLabel;
3142

32-
constructor(mdStepper: MdStepper) {
43+
/** Original ErrorStateMatcher that checks the validity of form control. */
44+
private _originalErrorStateMatcher: ErrorStateMatcher;
45+
46+
constructor(mdStepper: MdStepper,
47+
@Optional() @SkipSelf() @Inject(MD_ERROR_GLOBAL_OPTIONS) errorOptions: ErrorOptions) {
3348
super(mdStepper);
49+
this._originalErrorStateMatcher =
50+
errorOptions ? errorOptions.errorStateMatcher || defaultErrorStateMatcher
51+
: defaultErrorStateMatcher;
52+
}
53+
54+
/** Custom error state matcher that additionally checks for validity of interacted form. */
55+
errorStateMatcher = (control: FormControl, form: FormGroupDirective | NgForm) => {
56+
let originalErrorState = this._originalErrorStateMatcher(control, form);
57+
let customErrorState = control.invalid && this.interacted;
58+
59+
return originalErrorState || customErrorState;
3460
}
3561
}
3662

src/lib/table/cell.ts

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,13 @@
77
*/
88

99
import {Directive, ElementRef, Input, Renderer2} from '@angular/core';
10-
import {CdkCell, CdkCellDef, CdkColumnDef, CdkHeaderCell, CdkHeaderCellDef} from '@angular/cdk/table';
10+
import {
11+
CdkCell,
12+
CdkCellDef,
13+
CdkColumnDef,
14+
CdkHeaderCell,
15+
CdkHeaderCellDef
16+
} from '@angular/cdk/table';
1117

1218
/** Workaround for https://github.com/angular/angular/issues/17849 */
1319
export const _MdCellDef = CdkCellDef;

src/lib/table/row.ts

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,13 @@
77
*/
88

99
import {ChangeDetectionStrategy, Component, Directive} from '@angular/core';
10-
import {CdkHeaderRow, CdkRow, CDK_ROW_TEMPLATE, CdkRowDef, CdkHeaderRowDef} from '@angular/cdk/table';
10+
import {
11+
CdkHeaderRow,
12+
CdkRow,
13+
CDK_ROW_TEMPLATE,
14+
CdkRowDef,
15+
CdkHeaderRowDef
16+
} from '@angular/cdk/table';
1117

1218
/** Workaround for https://github.com/angular/angular/issues/17849 */
1319
export const _MdHeaderRowDef = CdkHeaderRowDef;

0 commit comments

Comments
 (0)