X Tutup
Skip to content

Commit 547e011

Browse files
committed
feat(forms): add support for Validator
Currently, the only way for a directive to export a validator is by providing a function. This makes it ackward to write validators that depend on directive inputs. In addition to supporting functions as validators, classes implementing the Validator interface are supported too.
1 parent 9c63a47 commit 547e011

File tree

12 files changed

+123
-64
lines changed

12 files changed

+123
-64
lines changed

modules/angular2/src/core/forms.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -37,7 +37,8 @@ export {NG_VALIDATORS, Validators} from './forms/validators';
3737
export {
3838
RequiredValidator,
3939
MinLengthValidator,
40-
MaxLengthValidator
40+
MaxLengthValidator,
41+
Validator
4142
} from './forms/directives/validators';
4243
export {FormBuilder} from './forms/form_builder';
4344

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

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
import {ControlValueAccessor} from './control_value_accessor';
22
import {AbstractControlDirective} from './abstract_control_directive';
3+
import {unimplemented} from 'angular2/src/core/facade/exceptions';
34

45
/**
56
* A base class that all control directive extend.
@@ -13,7 +14,7 @@ export class NgControl extends AbstractControlDirective {
1314
name: string = null;
1415
valueAccessor: ControlValueAccessor = null;
1516

16-
get validator(): Function { return null; }
17+
get validator(): Function { return unimplemented(); }
1718

18-
viewToModelUpdate(newValue: any): void {}
19+
viewToModelUpdate(newValue: any): void { return unimplemented(); }
1920
}

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

Lines changed: 7 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@ import {forwardRef, Host, SkipSelf, Provider, Inject, Optional} from 'angular2/s
88
import {ControlContainer} from './control_container';
99
import {NgControl} from './ng_control';
1010
import {ControlValueAccessor, NG_VALUE_ACCESSOR} from './control_value_accessor';
11-
import {controlPath, isPropertyUpdated, selectValueAccessor} from './shared';
11+
import {controlPath, composeValidators, isPropertyUpdated, selectValueAccessor} from './shared';
1212
import {Control} from '../model';
1313
import {Validators, NG_VALIDATORS} from '../validators';
1414

@@ -85,16 +85,17 @@ export class NgControlName extends NgControl implements OnChanges,
8585
update = new EventEmitter();
8686
model: any;
8787
viewModel: any;
88-
validators: Function[];
88+
private _validator: Function;
8989
/** @internal */
9090
_added = false;
9191

9292
constructor(@Host() @SkipSelf() parent: ControlContainer,
93-
@Optional() @Inject(NG_VALIDATORS) validators: Function[],
93+
@Optional() @Inject(NG_VALIDATORS) validators:
94+
/* Array<Validator|Function> */ any[],
9495
@Optional() @Inject(NG_VALUE_ACCESSOR) valueAccessors: ControlValueAccessor[]) {
9596
super();
9697
this._parent = parent;
97-
this.validators = validators;
98+
this._validator = composeValidators(validators);
9899
this.valueAccessor = selectValueAccessor(this, valueAccessors);
99100
}
100101

@@ -120,7 +121,7 @@ export class NgControlName extends NgControl implements OnChanges,
120121

121122
get formDirective(): any { return this._parent.formDirective; }
122123

123-
get control(): Control { return this.formDirective.getControl(this); }
124+
get validator(): Function { return this._validator; }
124125

125-
get validator(): Function { return Validators.compose(this.validators); }
126+
get control(): Control { return this.formDirective.getControl(this); }
126127
}

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

Lines changed: 7 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@ import {NgControl} from './ng_control';
99
import {Control} from '../model';
1010
import {Validators, NG_VALIDATORS} from '../validators';
1111
import {ControlValueAccessor, NG_VALUE_ACCESSOR} from './control_value_accessor';
12-
import {setUpControl, isPropertyUpdated, selectValueAccessor} from './shared';
12+
import {setUpControl, composeValidators, isPropertyUpdated, selectValueAccessor} from './shared';
1313

1414
const formControlBinding =
1515
CONST_EXPR(new Provider(NgControl, {useExisting: forwardRef(() => NgFormControl)}));
@@ -73,12 +73,13 @@ export class NgFormControl extends NgControl implements OnChanges {
7373
update = new EventEmitter();
7474
model: any;
7575
viewModel: any;
76-
validators: Function[];
76+
private _validator: Function;
7777

78-
constructor(@Optional() @Inject(NG_VALIDATORS) validators: Function[],
78+
constructor(@Optional() @Inject(NG_VALIDATORS) validators:
79+
/* Array<Validator|Function> */ any[],
7980
@Optional() @Inject(NG_VALUE_ACCESSOR) valueAccessors: ControlValueAccessor[]) {
8081
super();
81-
this.validators = validators;
82+
this._validator = composeValidators(validators);
8283
this.valueAccessor = selectValueAccessor(this, valueAccessors);
8384
}
8485

@@ -95,9 +96,9 @@ export class NgFormControl extends NgControl implements OnChanges {
9596

9697
get path(): string[] { return []; }
9798

98-
get control(): Control { return this.form; }
99+
get validator(): Function { return this._validator; }
99100

100-
get validator(): Function { return Validators.compose(this.validators); }
101+
get control(): Control { return this.form; }
101102

102103
viewToModelUpdate(newValue: any): void {
103104
this.viewModel = newValue;

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

Lines changed: 6 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@ import {ControlValueAccessor, NG_VALUE_ACCESSOR} from './control_value_accessor'
88
import {NgControl} from './ng_control';
99
import {Control} from '../model';
1010
import {Validators, NG_VALIDATORS} from '../validators';
11-
import {setUpControl, isPropertyUpdated, selectValueAccessor} from './shared';
11+
import {setUpControl, isPropertyUpdated, selectValueAccessor, composeValidators} from './shared';
1212

1313
const formControlBinding =
1414
CONST_EXPR(new Provider(NgControl, {useExisting: forwardRef(() => NgModel)}));
@@ -49,12 +49,13 @@ export class NgModel extends NgControl implements OnChanges {
4949
update = new EventEmitter();
5050
model: any;
5151
viewModel: any;
52-
validators: Function[];
52+
private _validator: Function;
5353

54-
constructor(@Optional() @Inject(NG_VALIDATORS) validators: Function[],
54+
constructor(@Optional() @Inject(NG_VALIDATORS) validators:
55+
/* Array<Validator|Function> */ any[],
5556
@Optional() @Inject(NG_VALUE_ACCESSOR) valueAccessors: ControlValueAccessor[]) {
5657
super();
57-
this.validators = validators;
58+
this._validator = composeValidators(validators);
5859
this.valueAccessor = selectValueAccessor(this, valueAccessors);
5960
}
6061

@@ -75,7 +76,7 @@ export class NgModel extends NgControl implements OnChanges {
7576

7677
get path(): string[] { return []; }
7778

78-
get validator(): Function { return Validators.compose(this.validators); }
79+
get validator(): Function { return this._validator; }
7980

8081
viewToModelUpdate(newValue: any): void {
8182
this.viewModel = newValue;
Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
library angular2.core.forms.normalize_validators;
2+
3+
import 'package:angular2/src/core/forms/directives/validators.dart' show Validator;
4+
5+
Function normalizeValidator(dynamic validator){
6+
if (validator is Validator) {
7+
return (c) => validator.validate(c);
8+
} else {
9+
return validator;
10+
}
11+
}
12+
Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
import {Validator} from './validators';
2+
3+
export function normalizeValidator(validator: Function | Validator): Function {
4+
if ((<Validator>validator).validate !== undefined) {
5+
return (c) => (<Validator>validator).validate(c);
6+
} else {
7+
return <Function>validator;
8+
}
9+
}

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

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@ import {DefaultValueAccessor} from './default_value_accessor';
1515
import {NumberValueAccessor} from './number_value_accessor';
1616
import {CheckboxControlValueAccessor} from './checkbox_value_accessor';
1717
import {SelectControlValueAccessor} from './select_control_value_accessor';
18+
import {normalizeValidator} from './normalize_validator';
1819

1920

2021
export function controlPath(name: string, parent: ControlContainer): string[] {
@@ -59,6 +60,12 @@ export function setProperty(renderer: Renderer, elementRef: ElementRef, propName
5960
renderer.setElementProperty(elementRef, propName, propValue);
6061
}
6162

63+
export function composeValidators(
64+
validators: /* Array<Validator|Function> */ any[]): Function {
65+
return isPresent(validators) ? Validators.compose(validators.map(normalizeValidator)) :
66+
Validators.nullValidator;
67+
}
68+
6269
export function isPropertyUpdated(changes: {[key: string]: any}, viewModel: any): boolean {
6370
if (!StringMapWrapper.contains(changes, "model")) return false;
6471
var change = changes["model"];

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

Lines changed: 39 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -2,8 +2,30 @@ import {forwardRef, Provider, OpaqueToken} from 'angular2/src/core/di';
22
import {CONST_EXPR} from 'angular2/src/core/facade/lang';
33
import {Attribute, Directive} from 'angular2/src/core/metadata';
44
import {Validators, NG_VALIDATORS} from '../validators';
5+
import {Control} from '../model';
6+
import * as modelModule from '../model';
57
import {NumberWrapper} from "angular2/src/core/facade/lang";
68

9+
10+
/**
11+
* An interface that can be implemented by classes that can act as validators.
12+
*
13+
* ## Usage
14+
*
15+
* ```typescript
16+
* @Directive({
17+
* selector: '[custom-validator]',
18+
* providers: [provide(NG_VALIDATORS, {useExisting: CustomValidatorDirective, multi: true})]
19+
* })
20+
* class CustomValidatorDirective implements Validator {
21+
* validate(c: Control): {[key: string]: any} {
22+
* return {"custom": true};
23+
* }
24+
* }
25+
* ```
26+
*/
27+
export interface Validator { validate(c: modelModule.Control): {[key: string]: any}; }
28+
729
const REQUIRED_VALIDATOR =
830
CONST_EXPR(new Provider(NG_VALIDATORS, {useValue: Validators.required, multi: true}));
931

@@ -14,40 +36,34 @@ const REQUIRED_VALIDATOR =
1436
export class RequiredValidator {
1537
}
1638

17-
function createMinLengthValidator(dir): any {
18-
return Validators.minLength(dir.minLength);
19-
}
20-
const MIN_LENGTH_VALIDATOR = CONST_EXPR(new Provider(NG_VALIDATORS, {
21-
useFactory: createMinLengthValidator,
22-
deps: [forwardRef(() => MinLengthValidator)],
23-
multi: true
24-
}));
39+
const MIN_LENGTH_VALIDATOR = CONST_EXPR(
40+
new Provider(NG_VALIDATORS, {useExisting: forwardRef(() => MinLengthValidator), multi: true}));
2541
@Directive({
2642
selector: '[minlength][ng-control],[minlength][ng-form-control],[minlength][ng-model]',
2743
providers: [MIN_LENGTH_VALIDATOR]
2844
})
29-
export class MinLengthValidator {
30-
minLength: number;
45+
export class MinLengthValidator implements Validator {
46+
private _validator: Function;
47+
3148
constructor(@Attribute("minlength") minLength: string) {
32-
this.minLength = NumberWrapper.parseInt(minLength, 10);
49+
this._validator = Validators.minLength(NumberWrapper.parseInt(minLength, 10));
3350
}
34-
}
3551

36-
function createMaxLengthValidator(dir): any {
37-
return Validators.maxLength(dir.maxLength);
52+
validate(c: Control): {[key: string]: any} { return this._validator(c); }
3853
}
39-
const MAX_LENGTH_VALIDATOR = CONST_EXPR(new Provider(NG_VALIDATORS, {
40-
useFactory: createMaxLengthValidator,
41-
deps: [forwardRef(() => MaxLengthValidator)],
42-
multi: true
43-
}));
54+
55+
const MAX_LENGTH_VALIDATOR = CONST_EXPR(
56+
new Provider(NG_VALIDATORS, {useExisting: forwardRef(() => MaxLengthValidator), multi: true}));
4457
@Directive({
4558
selector: '[maxlength][ng-control],[maxlength][ng-form-control],[maxlength][ng-model]',
4659
providers: [MAX_LENGTH_VALIDATOR]
4760
})
48-
export class MaxLengthValidator {
49-
maxLength: number;
50-
constructor(@Attribute("maxlength") maxLength: string) {
51-
this.maxLength = NumberWrapper.parseInt(maxLength, 10);
61+
export class MaxLengthValidator implements Validator {
62+
private _validator: Function;
63+
64+
constructor(@Attribute("maxlength") minLength: string) {
65+
this._validator = Validators.maxLength(NumberWrapper.parseInt(minLength, 10));
5266
}
67+
68+
validate(c: Control): {[key: string]: any} { return this._validator(c); }
5369
}

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

Lines changed: 25 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -32,11 +32,12 @@ import {
3232
DefaultValueAccessor,
3333
CheckboxControlValueAccessor,
3434
SelectControlValueAccessor,
35-
QueryList
35+
QueryList,
36+
Validator
3637
} from 'angular2/core';
3738

3839

39-
import {selectValueAccessor} from 'angular2/src/core/forms/directives/shared';
40+
import {selectValueAccessor, composeValidators} from 'angular2/src/core/forms/directives/shared';
4041

4142
import {SimpleChange} from 'angular2/src/core/change_detection';
4243

@@ -49,6 +50,10 @@ class DummyControlValueAccessor implements ControlValueAccessor {
4950
writeValue(obj: any): void { this.writtenValue = obj; }
5051
}
5152

53+
class CustomValidatorDirective implements Validator {
54+
validate(c: Control): {[key: string]: any} { return {"custom": true}; }
55+
}
56+
5257
export function main() {
5358
describe("Form Directives", () => {
5459
var defaultAccessor;
@@ -97,6 +102,21 @@ export function main() {
97102
expect(() => selectValueAccessor(dir, [customAccessor, customAccessor])).toThrowError();
98103
});
99104
});
105+
106+
describe("composeValidators", () => {
107+
it("should compose functions", () => {
108+
var dummy1 = (_) => ({"dummy1": true});
109+
var dummy2 = (_) => ({"dummy2": true});
110+
var v = composeValidators([dummy1, dummy2]);
111+
expect(v(new Control(""))).toEqual({"dummy1": true, "dummy2": true});
112+
});
113+
114+
it("should compose validator directives", () => {
115+
var dummy1 = (_) => ({"dummy1": true});
116+
var v = composeValidators([dummy1, new CustomValidatorDirective()]);
117+
expect(v(new Control(""))).toEqual({"dummy1": true, "custom": true});
118+
});
119+
});
100120
});
101121

102122
describe("NgFormModel", () => {
@@ -113,7 +133,7 @@ export function main() {
113133
});
114134
form.form = formModel;
115135

116-
loginControlDir = new NgControlName(form, [], [defaultAccessor]);
136+
loginControlDir = new NgControlName(form, [Validators.required], [defaultAccessor]);
117137
loginControlDir.name = "login";
118138
loginControlDir.valueAccessor = new DummyControlValueAccessor();
119139
});
@@ -147,8 +167,6 @@ export function main() {
147167
});
148168

149169
it("should set up validator", () => {
150-
loginControlDir.validators = [Validators.required];
151-
152170
expect(formModel.find(["login"]).valid).toBe(true);
153171

154172
// this will add the required validator and recalculate the validity
@@ -335,7 +353,7 @@ export function main() {
335353
};
336354

337355
beforeEach(() => {
338-
controlDir = new NgFormControl([], [defaultAccessor]);
356+
controlDir = new NgFormControl([Validators.required], [defaultAccessor]);
339357
controlDir.valueAccessor = new DummyControlValueAccessor();
340358

341359
control = new Control(null);
@@ -353,8 +371,6 @@ export function main() {
353371
});
354372

355373
it("should set up validator", () => {
356-
controlDir.validators = [Validators.required];
357-
358374
expect(control.valid).toBe(true);
359375

360376
// this will add the required validator and recalculate the validity
@@ -368,7 +384,7 @@ export function main() {
368384
var ngModel;
369385

370386
beforeEach(() => {
371-
ngModel = new NgModel([], [defaultAccessor]);
387+
ngModel = new NgModel([Validators.required], [defaultAccessor]);
372388
ngModel.valueAccessor = new DummyControlValueAccessor();
373389
});
374390

@@ -385,8 +401,6 @@ export function main() {
385401
});
386402

387403
it("should set up validator", () => {
388-
ngModel.validators = [Validators.required];
389-
390404
expect(ngModel.control.valid).toBe(true);
391405

392406
// this will add the required validator and recalculate the validity

0 commit comments

Comments
 (0)
X Tutup