Skip to content

Commit 5d78514

Browse files
committed
Modify form controls for stepper-demo and add custom validator
1 parent 74b24ce commit 5d78514

File tree

6 files changed

+82
-71
lines changed

6 files changed

+82
-71
lines changed

src/cdk/stepper/stepper-button.ts

Lines changed: 2 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -12,10 +12,7 @@ import {CdkStepper} from './stepper';
1212
/** Button that moves to the next step in a stepper workflow. */
1313
@Directive({
1414
selector: 'button[cdkStepperNext]',
15-
host: {
16-
'(click)': '_stepper.next()',
17-
'type': 'button'
18-
}
15+
host: {'(click)': '_stepper.next()'}
1916
})
2017
export class CdkStepperNext {
2118
constructor(public _stepper: CdkStepper) { }
@@ -24,10 +21,7 @@ export class CdkStepperNext {
2421
/** Button that moves to the previous step in a stepper workflow. */
2522
@Directive({
2623
selector: 'button[cdkStepperPrevious]',
27-
host: {
28-
'(click)': '_stepper.previous()',
29-
'type': 'button'
30-
}
24+
host: {'(click)': '_stepper.previous()'}
3125
})
3226
export class CdkStepperPrevious {
3327
constructor(public _stepper: CdkStepper) { }

src/cdk/stepper/stepper.ts

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -55,6 +55,7 @@ export class CdkStep {
5555
/** Template for step content. */
5656
@ViewChild(TemplateRef) content: TemplateRef<any>;
5757

58+
// TODO(jwshin): use disabled mixin when moved to cdk.
5859
/** Whether step is disabled or not. */
5960
@Input()
6061
get disabled() { return this._disabled; }
@@ -63,10 +64,10 @@ export class CdkStep {
6364
}
6465
private _disabled = false;
6566

66-
/** Whether the user has interacted with step or not. */
67+
/** Whether user has seen the expanded step content or not . */
6768
get interacted() { return this._interacted; }
68-
set interacted(value: any) {
69-
this._interacted = coerceBooleanProperty(value);
69+
set interacted(value: boolean) {
70+
this._interacted = value;
7071
}
7172
private _interacted = false;
7273

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

Lines changed: 41 additions & 43 deletions
Original file line numberDiff line numberDiff line change
@@ -1,50 +1,48 @@
11
<h2>Linear Vertical Stepper Demo</h2>
2-
<form [formGroup]="formGroup" novalidate>
3-
<div formArrayName="formArray">
4-
<md-vertical-stepper>
5-
<md-step>
6-
<div [formGroupName]="0">
7-
<ng-template mdStepLabel>Fill out your name</ng-template>
8-
<md-input-container>
9-
<input mdInput placeholder="First Name" formControlName="firstNameFormCtrl" required>
10-
<md-error>This field is required</md-error>
11-
</md-input-container>
2+
<form [formGroup]="formGroup">
3+
<md-vertical-stepper formArrayName="formArray">
4+
<md-step formGroupName="0">
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>
1210

13-
<md-input-container>
14-
<input mdInput placeholder="Last Name" formControlName="lastNameFormCtrl" required>
15-
<md-error>This field is required</md-error>
16-
</md-input-container>
17-
<div>
18-
<button md-button mdStepperNext>Next</button>
19-
</div>
20-
</div>
21-
</md-step>
11+
<md-input-container>
12+
<input mdInput placeholder="Last Name" formControlName="lastNameFormCtrl" required>
13+
<md-error>This field is required</md-error>
14+
</md-input-container>
15+
<div>
16+
<button md-button mdStepperNext type="button">Next</button>
17+
</div>
18+
</md-step>
2219

23-
<md-step [disabled]="!formGroup.controls.formArray.controls[0].valid">
24-
<div [formGroupName]="1">
25-
<ng-template mdStepLabel>
26-
<div>Fill out your phone number</div>
27-
</ng-template>
28-
<md-input-container>
29-
<input mdInput placeholder="Phone number" formControlName="phoneFormCtrl" required>
30-
<md-error>This field is required</md-error>
31-
</md-input-container>
32-
<div>
33-
<button md-button mdStepperPrevious>Back</button>
34-
<button md-button mdStepperNext>Next</button>
35-
</div>
36-
</div>
37-
</md-step>
20+
<md-step formGroupName="1"
21+
[disabled]="formArray.hasError('invalid step') ?
22+
formArray.getError('invalid step').index <= 1 : false">
23+
<ng-template mdStepLabel>
24+
<div>Fill out your phone number</div>
25+
</ng-template>
26+
<md-input-container>
27+
<input mdInput placeholder="Phone number" formControlName="phoneFormCtrl">
28+
<md-error>This field is required</md-error>
29+
</md-input-container>
30+
<div>
31+
<button md-button mdStepperPrevious type="button">Back</button>
32+
<button md-button mdStepperNext type="button">Next</button>
33+
</div>
34+
</md-step>
3835

39-
<md-step [disabled]="!formGroup.controls.formArray.controls[1].valid">
40-
<ng-template mdStepLabel>Confirm your information</ng-template>
41-
Everything seems correct.
42-
<div>
43-
<button md-button>Done</button>
44-
</div>
45-
</md-step>
46-
</md-vertical-stepper>
47-
</div>
36+
<md-step
37+
[disabled]="formArray.hasError('invalid step') ?
38+
formArray.getError('invalid step').index <= 2 : false">
39+
<ng-template mdStepLabel>Confirm your information</ng-template>
40+
Everything seems correct.
41+
<div>
42+
<button md-button>Done</button>
43+
</div>
44+
</md-step>
45+
</md-vertical-stepper>
4846
</form>
4947

5048
<h2>Horizontal Stepper Demo</h2>

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

Lines changed: 26 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,7 @@
11
import {Component} from '@angular/core';
2-
import {Validators, FormBuilder, FormGroup} from '@angular/forms';
2+
import {
3+
Validators, FormBuilder, FormGroup, FormArray, ValidationErrors, ValidatorFn
4+
} from '@angular/forms';
35

46
@Component({
57
moduleId: module.id,
@@ -17,19 +19,35 @@ export class StepperDemo {
1719
{label: 'You are now done', content: 'Finished!'}
1820
];
1921

20-
constructor(private _fb: FormBuilder) { }
22+
/** Returns a FormArray with the name 'formArray'. */
23+
get formArray() { return this.formGroup.get('formArray'); }
24+
25+
constructor(private _formBuilder: FormBuilder) { }
2126

2227
ngOnInit() {
23-
this.formGroup = this._fb.group({
24-
formArray: this._fb.array([
25-
this._fb.group({
28+
this.formGroup = this._formBuilder.group({
29+
formArray: this._formBuilder.array([
30+
this._formBuilder.group({
2631
firstNameFormCtrl: ['', Validators.required],
2732
lastNameFormCtrl: ['', Validators.required],
2833
}),
29-
this._fb.group({
30-
phoneFormCtrl: ['', Validators.required],
34+
this._formBuilder.group({
35+
phoneFormCtrl: [''],
3136
})
32-
])
37+
], this._stepValidator)
3338
});
3439
}
40+
41+
/**
42+
* Form array validator to check if all form groups in form array are valid.
43+
* If not, it will return the index of the first invalid form group.
44+
*/
45+
private _stepValidator: ValidatorFn = (formArray: FormArray): ValidationErrors | null => {
46+
for (let i = 0; i < formArray.length; i++) {
47+
if (formArray.at(i).invalid) {
48+
return {'invalid step': {'index': i}};
49+
}
50+
}
51+
return null;
52+
}
3553
}

src/lib/stepper/stepper-button.ts

Lines changed: 2 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -13,21 +13,15 @@ import {MdStepper} from './stepper';
1313
/** Button that moves to the next step in a stepper workflow. */
1414
@Directive({
1515
selector: 'button[mdStepperNext], button[matStepperNext]',
16-
host: {
17-
'(click)': '_stepper.next()',
18-
'type': 'button'
19-
},
16+
host: {'(click)': '_stepper.next()'},
2017
providers: [{provide: CdkStepper, useExisting: MdStepper}]
2118
})
2219
export class MdStepperNext extends CdkStepperNext { }
2320

2421
/** Button that moves to the previous step in a stepper workflow. */
2522
@Directive({
2623
selector: 'button[mdStepperPrevious], button[matStepperPrevious]',
27-
host: {
28-
'(click)': '_stepper.previous()',
29-
'type': 'button'
30-
},
24+
host: {'(click)': '_stepper.previous()'},
3125
providers: [{provide: CdkStepper, useExisting: MdStepper}]
3226
})
3327
export class MdStepperPrevious extends CdkStepperPrevious { }

src/lib/stepper/stepper.ts

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -36,7 +36,7 @@ import {FormControl, FormGroupDirective, NgForm} from '@angular/forms';
3636
templateUrl: 'step.html',
3737
providers: [{provide: MD_ERROR_GLOBAL_OPTIONS, useExisting: MdStep}]
3838
})
39-
export class MdStep extends CdkStep {
39+
export class MdStep extends CdkStep implements ErrorOptions {
4040
/** Content for step label given by <ng-template matStepLabel> or <ng-template mdStepLabel>. */
4141
@ContentChild(MdStepLabel) stepLabel: MdStepLabel;
4242

@@ -54,6 +54,12 @@ export class MdStep extends CdkStep {
5454
/** Custom error state matcher that additionally checks for validity of interacted form. */
5555
errorStateMatcher = (control: FormControl, form: FormGroupDirective | NgForm) => {
5656
let originalErrorState = this._originalErrorStateMatcher(control, form);
57+
58+
/**
59+
* Custom error state checks for the validity of form that is not submitted or touched
60+
* since user can trigger a form change by calling for another step without directly
61+
* interacting with the current form.
62+
*/
5763
let customErrorState = control.invalid && this.interacted;
5864

5965
return originalErrorState || customErrorState;

0 commit comments

Comments
 (0)