refactor(forms): use multibindings instead of query to get a list of validators

BREAKING CHANGE

Before:

@Directive({selector: '[credit-card]', bindings: [new Binding(NgValidator, {toAlias: forwardRef(() => CreditCardValidator)})]})
class CreditCardValidator {
  get validator() { return CreditCardValidator.validate; }
  static validate(c): StringMap<string, boolean> {...}
}

After:

function creditCardValidator(c): StringMap<string, boolean> {...}
@Directive({selector: '[credit-card]', bindings: [new Binding(NG_VALIDATORS, {toValue: creditCardValidator, multi: true})]})
class CreditCardValidator {}
This commit is contained in:
vsavkin 2015-09-02 10:24:22 -07:00 committed by Victor Savkin
parent 7736964a37
commit 79994b2abf
10 changed files with 50 additions and 55 deletions

View File

@ -32,8 +32,8 @@ export {
SelectControlValueAccessor SelectControlValueAccessor
} from './src/forms/directives/select_control_value_accessor'; } from './src/forms/directives/select_control_value_accessor';
export {FORM_DIRECTIVES} from './src/forms/directives'; export {FORM_DIRECTIVES} from './src/forms/directives';
export {Validators} from './src/forms/validators'; export {NG_VALIDATORS, Validators} from './src/forms/validators';
export {NgValidator, NgRequiredValidator} from './src/forms/directives/validators'; export {DefaultValidators} from './src/forms/directives/validators';
export {FormBuilder} from './src/forms/form_builder'; export {FormBuilder} from './src/forms/form_builder';
import {FormBuilder} from './src/forms/form_builder'; import {FormBuilder} from './src/forms/form_builder';

View File

@ -11,7 +11,7 @@ import {
SelectControlValueAccessor, SelectControlValueAccessor,
NgSelectOption NgSelectOption
} from './directives/select_control_value_accessor'; } from './directives/select_control_value_accessor';
import {NgRequiredValidator} from './directives/validators'; import {DefaultValidators} from './directives/validators';
export {NgControlName} from './directives/ng_control_name'; export {NgControlName} from './directives/ng_control_name';
export {NgFormControl} from './directives/ng_form_control'; export {NgFormControl} from './directives/ng_form_control';
@ -27,7 +27,7 @@ export {
SelectControlValueAccessor, SelectControlValueAccessor,
NgSelectOption NgSelectOption
} from './directives/select_control_value_accessor'; } from './directives/select_control_value_accessor';
export {NgValidator, NgRequiredValidator} from './directives/validators'; export {DefaultValidators} from './directives/validators';
/** /**
* *
@ -49,5 +49,5 @@ export const FORM_DIRECTIVES: Type[] = CONST_EXPR([
CheckboxControlValueAccessor, CheckboxControlValueAccessor,
SelectControlValueAccessor, SelectControlValueAccessor,
NgRequiredValidator DefaultValidators
]); ]);

View File

@ -4,13 +4,14 @@ import {StringMap} from 'angular2/src/core/facade/collection';
import {QueryList} from 'angular2/core'; import {QueryList} from 'angular2/core';
import {Query, Directive, LifecycleEvent} from 'angular2/metadata'; import {Query, Directive, LifecycleEvent} from 'angular2/metadata';
import {forwardRef, Host, SkipSelf, Binding, Inject} from 'angular2/di'; import {forwardRef, Host, SkipSelf, Binding, Inject, Optional} from 'angular2/di';
import {ControlContainer} from './control_container'; import {ControlContainer} from './control_container';
import {NgControl} from './ng_control'; import {NgControl} from './ng_control';
import {NgValidator} from './validators'; import {controlPath, isPropertyUpdated} from './shared';
import {controlPath, composeNgValidator, isPropertyUpdated} from './shared';
import {Control} from '../model'; import {Control} from '../model';
import {Validators, NG_VALIDATORS} from '../validators';
const controlNameBinding = const controlNameBinding =
CONST_EXPR(new Binding(NgControl, {toAlias: forwardRef(() => NgControlName)})); CONST_EXPR(new Binding(NgControl, {toAlias: forwardRef(() => NgControlName)}));
@ -84,15 +85,15 @@ export class NgControlName extends NgControl {
update = new EventEmitter(); update = new EventEmitter();
model: any; model: any;
viewModel: any; viewModel: any;
ngValidators: QueryList<NgValidator>; validators: Function[];
_added = false; _added = false;
// Scope the query once https://github.com/angular/angular/issues/2603 is fixed // Scope the query once https://github.com/angular/angular/issues/2603 is fixed
constructor(@Host() @SkipSelf() parent: ControlContainer, constructor(@Host() @SkipSelf() parent: ControlContainer,
@Query(NgValidator) ngValidators: QueryList<NgValidator>) { @Optional() @Inject(NG_VALIDATORS) validators: Function[]) {
super(); super();
this._parent = parent; this._parent = parent;
this.ngValidators = ngValidators; this.validators = validators;
} }
onChanges(c: StringMap<string, any>) { onChanges(c: StringMap<string, any>) {
@ -119,5 +120,5 @@ export class NgControlName extends NgControl {
get control(): Control { return this.formDirective.getControl(this); } get control(): Control { return this.formDirective.getControl(this); }
get validator(): Function { return composeNgValidator(this.ngValidators); } get validator(): Function { return Validators.compose(this.validators); }
} }

View File

@ -3,12 +3,12 @@ import {EventEmitter, ObservableWrapper} from 'angular2/src/core/facade/async';
import {QueryList} from 'angular2/core'; import {QueryList} from 'angular2/core';
import {Query, Directive, LifecycleEvent} from 'angular2/metadata'; import {Query, Directive, LifecycleEvent} from 'angular2/metadata';
import {forwardRef, Binding} from 'angular2/di'; import {forwardRef, Binding, Inject, Optional} from 'angular2/di';
import {NgControl} from './ng_control'; import {NgControl} from './ng_control';
import {Control} from '../model'; import {Control} from '../model';
import {NgValidator} from './validators'; import {Validators, NG_VALIDATORS} from '../validators';
import {setUpControl, composeNgValidator, isPropertyUpdated} from './shared'; import {setUpControl, isPropertyUpdated} from './shared';
const formControlBinding = const formControlBinding =
CONST_EXPR(new Binding(NgControl, {toAlias: forwardRef(() => NgFormControl)})); CONST_EXPR(new Binding(NgControl, {toAlias: forwardRef(() => NgFormControl)}));
@ -72,12 +72,11 @@ export class NgFormControl extends NgControl {
_added = false; _added = false;
model: any; model: any;
viewModel: any; viewModel: any;
ngValidators: QueryList<NgValidator>; validators: Function[];
// Scope the query once https://github.com/angular/angular/issues/2603 is fixed constructor(@Optional() @Inject(NG_VALIDATORS) validators: Function[]) {
constructor(@Query(NgValidator) ngValidators: QueryList<NgValidator>) {
super(); super();
this.ngValidators = ngValidators; this.validators = validators;
} }
onChanges(c: StringMap<string, any>) { onChanges(c: StringMap<string, any>) {
@ -95,7 +94,7 @@ export class NgFormControl extends NgControl {
get control(): Control { return this.form; } get control(): Control { return this.form; }
get validator(): Function { return composeNgValidator(this.ngValidators); } get validator(): Function { return Validators.compose(this.validators); }
viewToModelUpdate(newValue: any): void { viewToModelUpdate(newValue: any): void {
this.viewModel = newValue; this.viewModel = newValue;

View File

@ -3,12 +3,12 @@ import {EventEmitter, ObservableWrapper} from 'angular2/src/core/facade/async';
import {QueryList} from 'angular2/core'; import {QueryList} from 'angular2/core';
import {Query, Directive, LifecycleEvent} from 'angular2/metadata'; import {Query, Directive, LifecycleEvent} from 'angular2/metadata';
import {forwardRef, Binding} from 'angular2/di'; import {forwardRef, Binding, Inject, Optional} from 'angular2/di';
import {NgControl} from './ng_control'; import {NgControl} from './ng_control';
import {Control} from '../model'; import {Control} from '../model';
import {NgValidator} from './validators'; import {Validators, NG_VALIDATORS} from '../validators';
import {setUpControl, composeNgValidator, isPropertyUpdated} from './shared'; import {setUpControl, isPropertyUpdated} from './shared';
const formControlBinding = CONST_EXPR(new Binding(NgControl, {toAlias: forwardRef(() => NgModel)})); const formControlBinding = CONST_EXPR(new Binding(NgControl, {toAlias: forwardRef(() => NgModel)}));
@ -42,12 +42,11 @@ export class NgModel extends NgControl {
update = new EventEmitter(); update = new EventEmitter();
model: any; model: any;
viewModel: any; viewModel: any;
ngValidators: QueryList<NgValidator>; validators: Function[];
// Scope the query once https://github.com/angular/angular/issues/2603 is fixed constructor(@Optional() @Inject(NG_VALIDATORS) validators: Function[]) {
constructor(@Query(NgValidator) ngValidators: QueryList<NgValidator>) {
super(); super();
this.ngValidators = ngValidators; this.validators = validators;
} }
onChanges(c: StringMap<string, any>) { onChanges(c: StringMap<string, any>) {
@ -66,7 +65,7 @@ export class NgModel extends NgControl {
get path(): string[] { return []; } get path(): string[] { return []; }
get validator(): Function { return composeNgValidator(this.ngValidators); } get validator(): Function { return Validators.compose(this.validators); }
viewToModelUpdate(newValue: any): void { viewToModelUpdate(newValue: any): void {
this.viewModel = newValue; this.viewModel = newValue;

View File

@ -3,7 +3,6 @@ import {isBlank, BaseException, looseIdentical} from 'angular2/src/core/facade/l
import {ControlContainer} from './control_container'; import {ControlContainer} from './control_container';
import {NgControl} from './ng_control'; import {NgControl} from './ng_control';
import {NgValidator} from './validators';
import {Control} from '../model'; import {Control} from '../model';
import {Validators} from '../validators'; import {Validators} from '../validators';
import {Renderer} from 'angular2/render'; import {Renderer} from 'angular2/render';
@ -37,11 +36,6 @@ export function setUpControl(c: Control, dir: NgControl) {
dir.valueAccessor.registerOnTouched(() => c.markAsTouched()); dir.valueAccessor.registerOnTouched(() => c.markAsTouched());
} }
export function composeNgValidator(ngValidators: QueryList<NgValidator>): Function {
if (isBlank(ngValidators)) return Validators.nullValidator;
return Validators.compose(ngValidators.map(v => v.validator));
}
function _throwError(dir: NgControl, message: string): void { function _throwError(dir: NgControl, message: string): void {
var path = ListWrapper.join(dir.path, " -> "); var path = ListWrapper.join(dir.path, " -> ");
throw new BaseException(`${message} '${path}'`); throw new BaseException(`${message} '${path}'`);

View File

@ -1,19 +1,14 @@
import {forwardRef, Binding} from 'angular2/di'; import {forwardRef, OpaqueToken, Binding} from 'angular2/di';
import {CONST_EXPR} from 'angular2/src/core/facade/lang'; import {CONST_EXPR} from 'angular2/src/core/facade/lang';
import {Directive} from 'angular2/metadata'; import {Directive} from 'angular2/metadata';
import {Validators} from '../validators'; import {Validators, NG_VALIDATORS} from '../validators';
export class NgValidator { const DEFAULT_VALIDATORS =
get validator(): Function { throw "Is not implemented"; } CONST_EXPR(new Binding(NG_VALIDATORS, {toValue: Validators.required, multi: true}));
}
const requiredValidatorBinding =
CONST_EXPR(new Binding(NgValidator, {toAlias: forwardRef(() => NgRequiredValidator)}));
@Directive({ @Directive({
selector: '[required][ng-control],[required][ng-form-control],[required][ng-model]', selector: '[required][ng-control],[required][ng-form-control],[required][ng-model]',
bindings: [requiredValidatorBinding] bindings: [DEFAULT_VALIDATORS]
}) })
export class NgRequiredValidator extends NgValidator { export class DefaultValidators {
get validator(): Function { return Validators.required; }
} }

View File

@ -1,8 +1,12 @@
import {isBlank, isPresent} from 'angular2/src/core/facade/lang'; import {isBlank, isPresent} from 'angular2/src/core/facade/lang';
import {CONST_EXPR} from 'angular2/src/core/facade/lang';
import {ListWrapper, StringMapWrapper} from 'angular2/src/core/facade/collection'; import {ListWrapper, StringMapWrapper} from 'angular2/src/core/facade/collection';
import {OpaqueToken} from 'angular2/di';
import * as modelModule from './model'; import * as modelModule from './model';
export const NG_VALIDATORS = CONST_EXPR(new OpaqueToken("NgValidators"));
/** /**
* Provides a set of validators used by form controls. * Provides a set of validators used by form controls.
* *
@ -20,6 +24,8 @@ export class Validators {
static nullValidator(c: any): StringMap<string, boolean> { return null; } static nullValidator(c: any): StringMap<string, boolean> { return null; }
static compose(validators: Function[]): Function { static compose(validators: Function[]): Function {
if (isBlank(validators)) return Validators.nullValidator;
return function(c: modelModule.Control) { return function(c: modelModule.Control) {
var res = ListWrapper.reduce(validators, (res, validator) => { var res = ListWrapper.reduce(validators, (res, validator) => {
var errors = validator(c); var errors = validator(c);

View File

@ -14,8 +14,6 @@ import {
inject inject
} from 'angular2/test_lib'; } from 'angular2/test_lib';
import {QueryList} from 'angular2/angular2';
import { import {
ControlGroup, ControlGroup,
Control, Control,
@ -27,7 +25,7 @@ import {
NgForm, NgForm,
NgModel, NgModel,
NgFormControl, NgFormControl,
NgRequiredValidator DefaultValidators
} from 'angular2/forms'; } from 'angular2/forms';
class DummyControlValueAccessor implements ControlValueAccessor { class DummyControlValueAccessor implements ControlValueAccessor {
@ -51,7 +49,7 @@ export function main() {
formModel = new ControlGroup({"login": new Control(null)}); formModel = new ControlGroup({"login": new Control(null)});
form.form = formModel; form.form = formModel;
loginControlDir = new NgControlName(form, new QueryList<any>()); loginControlDir = new NgControlName(form, []);
loginControlDir.name = "login"; loginControlDir.name = "login";
loginControlDir.valueAccessor = new DummyControlValueAccessor(); loginControlDir.valueAccessor = new DummyControlValueAccessor();
}); });
@ -85,7 +83,7 @@ export function main() {
}); });
it("should set up validator", () => { it("should set up validator", () => {
loginControlDir.ngValidators.reset([new NgRequiredValidator()]); loginControlDir.validators = [Validators.required];
expect(formModel.find(["login"]).valid).toBe(true); expect(formModel.find(["login"]).valid).toBe(true);
@ -220,7 +218,7 @@ export function main() {
var control; var control;
beforeEach(() => { beforeEach(() => {
controlDir = new NgFormControl(new QueryList<any>()); controlDir = new NgFormControl([]);
controlDir.valueAccessor = new DummyControlValueAccessor(); controlDir.valueAccessor = new DummyControlValueAccessor();
control = new Control(null); control = new Control(null);
@ -239,7 +237,7 @@ export function main() {
}); });
it("should set up validator", () => { it("should set up validator", () => {
controlDir.ngValidators.reset([new NgRequiredValidator()]); controlDir.validators = [Validators.required];
expect(control.valid).toBe(true); expect(control.valid).toBe(true);
@ -254,7 +252,7 @@ export function main() {
var ngModel; var ngModel;
beforeEach(() => { beforeEach(() => {
ngModel = new NgModel(new QueryList<any>()); ngModel = new NgModel([]);
ngModel.valueAccessor = new DummyControlValueAccessor(); ngModel.valueAccessor = new DummyControlValueAccessor();
}); });
@ -271,7 +269,7 @@ export function main() {
}); });
it("should set up validator", () => { it("should set up validator", () => {
ngModel.ngValidators.reset([new NgRequiredValidator()]); ngModel.validators = [Validators.required];
expect(ngModel.control.valid).toBe(true); expect(ngModel.control.valid).toBe(true);
@ -291,7 +289,7 @@ export function main() {
var parent = new NgFormModel(); var parent = new NgFormModel();
parent.form = new ControlGroup({"name": formModel}); parent.form = new ControlGroup({"name": formModel});
controlNameDir = new NgControlName(parent, new QueryList<any>()); controlNameDir = new NgControlName(parent, []);
controlNameDir.name = "name"; controlNameDir.name = "name";
}); });

View File

@ -33,6 +33,9 @@ export function main() {
}); });
describe("compose", () => { describe("compose", () => {
it("should return a null validator when given null",
() => { expect(Validators.compose(null)).toBe(Validators.nullValidator); });
it("should collect errors from all the validators", () => { it("should collect errors from all the validators", () => {
var c = Validators.compose([validator("a", true), validator("b", true)]); var c = Validators.compose([validator("a", true), validator("b", true)]);
expect(c(new Control(""))).toEqual({"a": true, "b": true}); expect(c(new Control(""))).toEqual({"a": true, "b": true});