X Tutup
Skip to content

Commit 7580628

Browse files
committed
feat(forms): support adding validators to ControlGroup via template
Closes #4954
1 parent f98faf0 commit 7580628

File tree

9 files changed

+137
-30
lines changed

9 files changed

+137
-30
lines changed

modules/angular2/src/core/forms/directives/abstract_control_directive.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,4 +21,6 @@ export class AbstractControlDirective {
2121
get touched(): boolean { return isPresent(this.control) ? this.control.touched : null; }
2222

2323
get untouched(): boolean { return isPresent(this.control) ? this.control.untouched : null; }
24+
25+
get path(): string[] { return null; }
2426
}

modules/angular2/src/core/forms/directives/ng_control.ts

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,6 @@ export class NgControl extends AbstractControlDirective {
1414
valueAccessor: ControlValueAccessor = null;
1515

1616
get validator(): Function { return null; }
17-
get path(): string[] { return null; }
1817

1918
viewToModelUpdate(newValue: any): void {}
2019
}

modules/angular2/src/core/forms/directives/ng_control_group.ts

Lines changed: 11 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,14 @@
11
import {OnInit, OnDestroy} from 'angular2/lifecycle_hooks';
22
import {Directive} from 'angular2/src/core/metadata';
3-
import {Inject, Host, SkipSelf, forwardRef, Provider} from 'angular2/src/core/di';
3+
import {Optional, Inject, Host, SkipSelf, forwardRef, Provider} from 'angular2/src/core/di';
44
import {ListWrapper} from 'angular2/src/core/facade/collection';
55
import {CONST_EXPR} from 'angular2/src/core/facade/lang';
66

77
import {ControlContainer} from './control_container';
88
import {controlPath} from './shared';
99
import {ControlGroup} from '../model';
1010
import {Form} from './form_interface';
11+
import {Validators, NG_VALIDATORS} from '../validators';
1112

1213
const controlGroupBinding =
1314
CONST_EXPR(new Provider(ControlContainer, {useExisting: forwardRef(() => NgControlGroup)}));
@@ -60,9 +61,14 @@ export class NgControlGroup extends ControlContainer implements OnInit,
6061
OnDestroy {
6162
/** @internal */
6263
_parent: ControlContainer;
63-
constructor(@Host() @SkipSelf() _parent: ControlContainer) {
64+
65+
private _validators: Function[];
66+
67+
constructor(@Host() @SkipSelf() parent: ControlContainer,
68+
@Optional() @Inject(NG_VALIDATORS) validators: Function[]) {
6469
super();
65-
this._parent = _parent;
70+
this._parent = parent;
71+
this._validators = validators;
6672
}
6773

6874
onInit(): void { this.formDirective.addControlGroup(this); }
@@ -74,4 +80,6 @@ export class NgControlGroup extends ControlContainer implements OnInit,
7480
get path(): string[] { return controlPath(this.name, this._parent); }
7581

7682
get formDirective(): Form { return this._parent.formDirective; }
83+
84+
get validator(): Function { return Validators.compose(this._validators); }
7785
}

modules/angular2/src/core/forms/directives/ng_form.ts

Lines changed: 10 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -7,13 +7,14 @@ import {
77
import {StringMapWrapper, ListWrapper} from 'angular2/src/core/facade/collection';
88
import {isPresent, isBlank, CONST_EXPR} from 'angular2/src/core/facade/lang';
99
import {Directive} from 'angular2/src/core/metadata';
10-
import {forwardRef, Provider} from 'angular2/src/core/di';
10+
import {forwardRef, Provider, Optional, Inject} from 'angular2/src/core/di';
1111
import {NgControl} from './ng_control';
1212
import {Form} from './form_interface';
1313
import {NgControlGroup} from './ng_control_group';
1414
import {ControlContainer} from './control_container';
1515
import {AbstractControl, ControlGroup, Control} from '../model';
16-
import {setUpControl} from './shared';
16+
import {setUpControl, setUpControlGroup} from './shared';
17+
import {Validators, NG_VALIDATORS} from '../validators';
1718

1819
const formDirectiveProvider =
1920
CONST_EXPR(new Provider(ControlContainer, {useExisting: forwardRef(() => NgForm)}));
@@ -87,9 +88,14 @@ const formDirectiveProvider =
8788
exportAs: 'form'
8889
})
8990
export class NgForm extends ControlContainer implements Form {
90-
form: ControlGroup = new ControlGroup({});
91+
form: ControlGroup;
9192
ngSubmit = new EventEmitter();
9293

94+
constructor(@Optional() @Inject(NG_VALIDATORS) validators: Function[]) {
95+
super();
96+
this.form = new ControlGroup({}, null, Validators.compose(validators));
97+
}
98+
9399
get formDirective(): Form { return this; }
94100

95101
get control(): ControlGroup { return this.form; }
@@ -124,6 +130,7 @@ export class NgForm extends ControlContainer implements Form {
124130
this._later(_ => {
125131
var container = this._findContainer(dir.path);
126132
var group = new ControlGroup({});
133+
setUpControlGroup(group, dir);
127134
container.addControl(dir.name, group);
128135
group.updateValueAndValidity({emitEvent: false});
129136
});

modules/angular2/src/core/forms/directives/ng_form_model.ts

Lines changed: 24 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,16 +1,18 @@
11
import {CONST_EXPR} from 'angular2/src/core/facade/lang';
2-
import {ListWrapper} from 'angular2/src/core/facade/collection';
2+
import {ListWrapper, StringMapWrapper} from 'angular2/src/core/facade/collection';
33
import {ObservableWrapper, EventEmitter} from 'angular2/src/core/facade/async';
4+
import {SimpleChange} from 'angular2/src/core/change_detection';
45

56
import {OnChanges} from 'angular2/lifecycle_hooks';
67
import {Directive} from 'angular2/src/core/metadata';
7-
import {forwardRef, Provider} from 'angular2/src/core/di';
8+
import {forwardRef, Provider, Inject, Optional} from 'angular2/src/core/di';
89
import {NgControl} from './ng_control';
910
import {NgControlGroup} from './ng_control_group';
1011
import {ControlContainer} from './control_container';
1112
import {Form} from './form_interface';
1213
import {Control, ControlGroup} from '../model';
13-
import {setUpControl} from './shared';
14+
import {setUpControl, setUpControlGroup} from './shared';
15+
import {Validators, NG_VALIDATORS} from '../validators';
1416

1517
const formDirectiveProvider =
1618
CONST_EXPR(new Provider(ControlContainer, {useExisting: forwardRef(() => NgFormModel)}));
@@ -100,8 +102,21 @@ export class NgFormModel extends ControlContainer implements Form,
100102
form: ControlGroup = null;
101103
directives: NgControl[] = [];
102104
ngSubmit = new EventEmitter();
105+
private _validators: Function[];
103106

104-
onChanges(_): void { this._updateDomValue(); }
107+
constructor(@Optional() @Inject(NG_VALIDATORS) validators: Function[]) {
108+
super();
109+
this._validators = validators;
110+
}
111+
112+
onChanges(changes: {[key: string]: SimpleChange}): void {
113+
if (StringMapWrapper.contains(changes, "form")) {
114+
var c = Validators.compose(this._validators);
115+
this.form.validator = Validators.compose([this.form.validator, c]);
116+
}
117+
118+
this._updateDomValue();
119+
}
105120

106121
get formDirective(): Form { return this; }
107122

@@ -120,7 +135,11 @@ export class NgFormModel extends ControlContainer implements Form,
120135

121136
removeControl(dir: NgControl): void { ListWrapper.remove(this.directives, dir); }
122137

123-
addControlGroup(dir: NgControlGroup) {}
138+
addControlGroup(dir: NgControlGroup) {
139+
var ctrl: any = this.form.find(dir.path);
140+
setUpControlGroup(ctrl, dir);
141+
ctrl.updateValueAndValidity({emitEvent: false});
142+
}
124143

125144
removeControlGroup(dir: NgControlGroup) {}
126145

modules/angular2/src/core/forms/directives/shared.ts

Lines changed: 9 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,9 @@ import {BaseException, WrappedException} from 'angular2/src/core/facade/exceptio
44

55
import {ControlContainer} from './control_container';
66
import {NgControl} from './ng_control';
7-
import {Control} from '../model';
7+
import {AbstractControlDirective} from './abstract_control_directive';
8+
import {NgControlGroup} from './ng_control_group';
9+
import {Control, ControlGroup} from '../model';
810
import {Validators} from '../validators';
911
import {ControlValueAccessor} from './control_value_accessor';
1012
import {ElementRef, QueryList} from 'angular2/src/core/linker';
@@ -42,7 +44,12 @@ export function setUpControl(control: Control, dir: NgControl): void {
4244
dir.valueAccessor.registerOnTouched(() => control.markAsTouched());
4345
}
4446

45-
function _throwError(dir: NgControl, message: string): void {
47+
export function setUpControlGroup(control: ControlGroup, dir: NgControlGroup) {
48+
if (isBlank(control)) _throwError(dir, "Cannot find control");
49+
control.validator = Validators.compose([control.validator, dir.validator]);
50+
}
51+
52+
function _throwError(dir: AbstractControlDirective, message: string): void {
4653
var path = dir.path.join(" -> ");
4754
throw new BaseException(`${message} '${path}'`);
4855
}

modules/angular2/test/core/forms/directives_spec.ts

Lines changed: 55 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -105,8 +105,12 @@ export function main() {
105105
var loginControlDir;
106106

107107
beforeEach(() => {
108-
form = new NgFormModel();
109-
formModel = new ControlGroup({"login": new Control(null)});
108+
form = new NgFormModel([]);
109+
formModel = new ControlGroup({
110+
"login": new Control(),
111+
"passwords":
112+
new ControlGroup({"password": new Control(), "passwordConfirm": new Control()})
113+
});
110114
form.form = formModel;
111115

112116
loginControlDir = new NgControlName(form, [], [defaultAccessor]);
@@ -167,6 +171,26 @@ export function main() {
167171
});
168172
});
169173

174+
describe("addControlGroup", () => {
175+
var matchingPasswordsValidator = (g) => {
176+
if (g.controls["password"].value != g.controls["passwordConfirm"].value) {
177+
return {"differentPasswords": true};
178+
} else {
179+
return null;
180+
}
181+
};
182+
183+
it("should set up validator", () => {
184+
var group = new NgControlGroup(form, [matchingPasswordsValidator]);
185+
group.name = "passwords";
186+
form.addControlGroup(group);
187+
188+
formModel.find(["passwords", "password"]).updateValue("somePassword");
189+
190+
expect(formModel.hasError("differentPasswords", ["passwords"])).toEqual(true);
191+
});
192+
});
193+
170194
describe("removeControl", () => {
171195
it("should remove the directive to the list of directives included in the form", () => {
172196
form.addControl(loginControlDir);
@@ -181,10 +205,22 @@ export function main() {
181205

182206
formModel.find(["login"]).updateValue("new value");
183207

184-
form.onChanges(null);
208+
form.onChanges({});
185209

186210
expect((<any>loginControlDir.valueAccessor).writtenValue).toEqual("new value");
187211
});
212+
213+
it("should set up validator", () => {
214+
var formValidator = (c) => ({"custom": true});
215+
var f = new NgFormModel([formValidator]);
216+
f.form = formModel;
217+
f.onChanges({"form": formModel});
218+
219+
// trigger validation
220+
formModel.controls["login"].updateValue("");
221+
222+
expect(formModel.errors).toEqual({"custom": true});
223+
});
188224
});
189225
});
190226

@@ -195,10 +231,10 @@ export function main() {
195231
var personControlGroupDir;
196232

197233
beforeEach(() => {
198-
form = new NgForm();
234+
form = new NgForm([]);
199235
formModel = form.form;
200236

201-
personControlGroupDir = new NgControlGroup(form);
237+
personControlGroupDir = new NgControlGroup(form, []);
202238
personControlGroupDir.name = "person";
203239

204240
loginControlDir = new NgControlName(personControlGroupDir, null, [defaultAccessor]);
@@ -246,6 +282,17 @@ export function main() {
246282

247283
// should update the form's value and validity
248284
});
285+
286+
it("should set up validator", fakeAsync(() => {
287+
var formValidator = (c) => ({"custom": true});
288+
var f = new NgForm([formValidator]);
289+
f.addControlGroup(personControlGroupDir);
290+
f.addControl(loginControlDir);
291+
292+
flushMicrotasks();
293+
294+
expect(f.form.errors).toEqual({"custom": true});
295+
}));
249296
});
250297

251298
describe("NgControlGroup", () => {
@@ -255,9 +302,9 @@ export function main() {
255302
beforeEach(() => {
256303
formModel = new ControlGroup({"login": new Control(null)});
257304

258-
var parent = new NgFormModel();
305+
var parent = new NgFormModel([]);
259306
parent.form = new ControlGroup({"group": formModel});
260-
controlGroupDir = new NgControlGroup(parent);
307+
controlGroupDir = new NgControlGroup(parent, []);
261308
controlGroupDir.name = "group";
262309
});
263310

@@ -356,7 +403,7 @@ export function main() {
356403
beforeEach(() => {
357404
formModel = new Control("name");
358405

359-
var parent = new NgFormModel();
406+
var parent = new NgFormModel([]);
360407
parent.form = new ControlGroup({"name": formModel});
361408
controlNameDir = new NgControlName(parent, [], [defaultAccessor]);
362409
controlNameDir.name = "name";

modules/angular2/test/core/forms/integration_spec.ts

Lines changed: 24 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,8 @@ import {
2424
ControlGroup,
2525
ControlValueAccessor,
2626
FORM_DIRECTIVES,
27+
NG_VALIDATORS,
28+
Provider,
2729
NgControl,
2830
NgIf,
2931
NgFor,
@@ -398,10 +400,10 @@ export function main() {
398400
var form = new ControlGroup(
399401
{"login": new Control(""), "min": new Control(""), "max": new Control("")});
400402

401-
var t = `<div [ng-form-model]="form">
402-
<input type="text" ng-control="login" required>
403-
<input type="text" ng-control="min" minlength="3">
404-
<input type="text" ng-control="max" maxlength="3">
403+
var t = `<div [ng-form-model]="form" login-is-empty-validator>
404+
<input type="text" ng-control="login" required>
405+
<input type="text" ng-control="min" minlength="3">
406+
<input type="text" ng-control="max" maxlength="3">
405407
</div>`;
406408

407409
tcb.overrideTemplate(MyComp, t).createAsync(MyComp).then((rootTC) => {
@@ -423,6 +425,8 @@ export function main() {
423425
expect(form.hasError("minlength", ["min"])).toEqual(true);
424426
expect(form.hasError("maxlength", ["max"])).toEqual(true);
425427

428+
expect(form.hasError("loginIsEmpty")).toEqual(true);
429+
426430
required.nativeElement.value = "1";
427431
minLength.nativeElement.value = "123";
428432
maxLength.nativeElement.value = "123";
@@ -914,8 +918,22 @@ class MyInput implements ControlValueAccessor {
914918
}
915919
}
916920

917-
@Component({selector: "my-comp"})
918-
@View({directives: [FORM_DIRECTIVES, WrappedValue, MyInput, NgIf, NgFor]})
921+
function loginIsEmptyGroupValidator(c: ControlGroup) {
922+
return c.controls["login"].value == "" ? {"loginIsEmpty": true} : null;
923+
}
924+
925+
@Directive({
926+
selector: '[login-is-empty-validator]',
927+
providers: [new Provider(NG_VALIDATORS, {useValue: loginIsEmptyGroupValidator, multi: true})]
928+
})
929+
class LoginIsEmptyValidator {
930+
}
931+
932+
@Component({
933+
selector: "my-comp",
934+
template: '',
935+
directives: [FORM_DIRECTIVES, WrappedValue, MyInput, NgIf, NgFor, LoginIsEmptyValidator]
936+
})
919937
class MyComp {
920938
form: any;
921939
name: string;

modules/angular2/test/public_api_spec.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -73,6 +73,7 @@ var NG_API = [
7373
'AbstractControlDirective.untouched',
7474
'AbstractControlDirective.valid',
7575
'AbstractControlDirective.value',
76+
'AbstractControlDirective.path',
7677
'AppRootUrl',
7778
'AppRootUrl.value',
7879
'AppRootUrl.value=',
@@ -657,6 +658,7 @@ var NG_API = [
657658
'NgControlGroup.untouched',
658659
'NgControlGroup.valid',
659660
'NgControlGroup.value',
661+
'NgControlGroup.validator',
660662
'NgControlStatus',
661663
'NgControlStatus.ngClassDirty',
662664
'NgControlStatus.ngClassInvalid',
@@ -1030,9 +1032,7 @@ var NG_API = [
10301032
'UpperCasePipe.transform()',
10311033
'UrlResolver',
10321034
'UrlResolver.resolve()',
1033-
'Validators#array()',
10341035
'Validators#compose()',
1035-
'Validators#group()',
10361036
'Validators#nullValidator()',
10371037
'Validators#required()',
10381038
'Validators#minLength()',

0 commit comments

Comments
 (0)
X Tutup