From 00a4b2e28fb38be3e03f9f2c94e3f5e7ef5b4d51 Mon Sep 17 00:00:00 2001 From: vsavkin Date: Wed, 30 Sep 2015 17:52:33 -0700 Subject: [PATCH] feat(forms): make NgControl -> NgValueAccessor dependency unidirectional Closes #4421 --- modules/angular2/src/core/forms.ts | 1 + .../directives/checkbox_value_accessor.ts | 21 +++-- .../directives/control_value_accessor.ts | 7 +- .../directives/default_value_accessor.ts | 20 ++--- .../core/forms/directives/ng_control_name.ts | 7 +- .../forms/directives/ng_control_status.ts | 2 +- .../core/forms/directives/ng_form_control.ts | 7 +- .../src/core/forms/directives/ng_model.ts | 7 +- .../select_control_value_accessor.ts | 16 ++-- .../src/core/forms/directives/shared.ts | 40 +++++++++- .../test/core/forms/directives_spec.ts | 76 +++++++++++++++++-- .../test/core/forms/integration_spec.ts | 1 + modules/angular2/test/core/spies.dart | 10 +++ modules/angular2/test/core/spies.ts | 4 + modules/angular2/test/public_api_spec.ts | 25 ++---- 15 files changed, 180 insertions(+), 64 deletions(-) diff --git a/modules/angular2/src/core/forms.ts b/modules/angular2/src/core/forms.ts index 576e96cd46..bd156d46d5 100644 --- a/modules/angular2/src/core/forms.ts +++ b/modules/angular2/src/core/forms.ts @@ -26,6 +26,7 @@ export {NgFormModel} from './forms/directives/ng_form_model'; export {NgForm} from './forms/directives/ng_form'; export {ControlValueAccessor} from './forms/directives/control_value_accessor'; export {DefaultValueAccessor} from './forms/directives/default_value_accessor'; +export {NgControlStatus} from './forms/directives/ng_control_status'; export {CheckboxControlValueAccessor} from './forms/directives/checkbox_value_accessor'; export { NgSelectOption, diff --git a/modules/angular2/src/core/forms/directives/checkbox_value_accessor.ts b/modules/angular2/src/core/forms/directives/checkbox_value_accessor.ts index e06aac3aa2..d9da0f0afd 100644 --- a/modules/angular2/src/core/forms/directives/checkbox_value_accessor.ts +++ b/modules/angular2/src/core/forms/directives/checkbox_value_accessor.ts @@ -1,13 +1,15 @@ import {Directive} from 'angular2/src/core/metadata'; import {Renderer} from 'angular2/src/core/render'; import {ElementRef} from 'angular2/src/core/compiler'; -import {Self} from 'angular2/src/core/di'; +import {Self, forwardRef, Binding} from 'angular2/src/core/di'; -import {NgControl} from './ng_control'; -import {ControlValueAccessor} from './control_value_accessor'; -import {isPresent} from 'angular2/src/core/facade/lang'; +import {NG_VALUE_ACCESSOR, ControlValueAccessor} from './control_value_accessor'; +import {CONST_EXPR} from 'angular2/src/core/facade/lang'; import {setProperty} from './shared'; +const CHECKBOX_VALUE_ACCESSOR = CONST_EXPR(new Binding( + NG_VALUE_ACCESSOR, {toAlias: forwardRef(() => CheckboxControlValueAccessor), multi: true})); + /** * The accessor for writing a value and listening to changes on a checkbox input element. * @@ -19,21 +21,16 @@ import {setProperty} from './shared'; @Directive({ selector: 'input[type=checkbox][ng-control],input[type=checkbox][ng-form-control],input[type=checkbox][ng-model]', - host: { - '(change)': 'onChange($event.target.checked)', - '(blur)': 'onTouched()' - } + host: {'(change)': 'onChange($event.target.checked)', '(blur)': 'onTouched()'}, + bindings: [CHECKBOX_VALUE_ACCESSOR] }) export class CheckboxControlValueAccessor implements ControlValueAccessor { onChange = (_) => {}; onTouched = () => {}; - constructor(@Self() cd: NgControl, private _renderer: Renderer, private _elementRef: ElementRef) { - cd.valueAccessor = this; - } + constructor(private _renderer: Renderer, private _elementRef: ElementRef) {} writeValue(value: any): void { setProperty(this._renderer, this._elementRef, "checked", value); } - registerOnChange(fn: (_: any) => {}): void { this.onChange = fn; } registerOnTouched(fn: () => {}): void { this.onTouched = fn; } } diff --git a/modules/angular2/src/core/forms/directives/control_value_accessor.ts b/modules/angular2/src/core/forms/directives/control_value_accessor.ts index df953dfc72..3ceba8f389 100644 --- a/modules/angular2/src/core/forms/directives/control_value_accessor.ts +++ b/modules/angular2/src/core/forms/directives/control_value_accessor.ts @@ -1,3 +1,6 @@ +import {CONST_EXPR} from 'angular2/src/core/facade/lang'; +import {OpaqueToken} from 'angular2/src/core/di'; + /** * A bridge between a control and a native element. * @@ -7,4 +10,6 @@ export interface ControlValueAccessor { writeValue(obj: any): void; registerOnChange(fn: any): void; registerOnTouched(fn: any): void; -} \ No newline at end of file +} + +export const NG_VALUE_ACCESSOR: OpaqueToken = CONST_EXPR(new OpaqueToken("NgValueAccessor")); \ No newline at end of file diff --git a/modules/angular2/src/core/forms/directives/default_value_accessor.ts b/modules/angular2/src/core/forms/directives/default_value_accessor.ts index 96fa235a43..c092c80ef9 100644 --- a/modules/angular2/src/core/forms/directives/default_value_accessor.ts +++ b/modules/angular2/src/core/forms/directives/default_value_accessor.ts @@ -1,12 +1,14 @@ import {Directive} from 'angular2/src/core/metadata'; import {ElementRef} from 'angular2/src/core/compiler'; import {Renderer} from 'angular2/src/core/render'; -import {Self} from 'angular2/src/core/di'; -import {NgControl} from './ng_control'; -import {ControlValueAccessor} from './control_value_accessor'; -import {isBlank, isPresent} from 'angular2/src/core/facade/lang'; +import {Self, forwardRef, Binding} from 'angular2/src/core/di'; +import {NG_VALUE_ACCESSOR, ControlValueAccessor} from './control_value_accessor'; +import {isBlank, CONST_EXPR} from 'angular2/src/core/facade/lang'; import {setProperty} from './shared'; +const DEFAULT_VALUE_ACCESSOR = CONST_EXPR( + new Binding(NG_VALUE_ACCESSOR, {toAlias: forwardRef(() => DefaultValueAccessor), multi: true})); + /** * The default accessor for writing a value and listening to changes that is used by the * {@link NgModel}, {@link NgFormControl}, and {@link NgControlName} directives. @@ -17,21 +19,19 @@ import {setProperty} from './shared'; * ``` */ @Directive({ - selector: - 'input:not([type=checkbox])[ng-control],textarea[ng-control],input:not([type=checkbox])[ng-form-control],textarea[ng-form-control],input:not([type=checkbox])[ng-model],textarea[ng-model]', + selector: '[ng-control],[ng-model],[ng-form-control]', host: { '(change)': 'onChange($event.target.value)', '(input)': 'onChange($event.target.value)', '(blur)': 'onTouched()' - } + }, + bindings: [DEFAULT_VALUE_ACCESSOR] }) export class DefaultValueAccessor implements ControlValueAccessor { onChange = (_) => {}; onTouched = () => {}; - constructor(@Self() cd: NgControl, private _renderer: Renderer, private _elementRef: ElementRef) { - cd.valueAccessor = this; - } + constructor(private _renderer: Renderer, private _elementRef: ElementRef) {} writeValue(value: any): void { var normalizedValue = isBlank(value) ? '' : value; diff --git a/modules/angular2/src/core/forms/directives/ng_control_name.ts b/modules/angular2/src/core/forms/directives/ng_control_name.ts index fa6ed854fe..15286ad1f8 100644 --- a/modules/angular2/src/core/forms/directives/ng_control_name.ts +++ b/modules/angular2/src/core/forms/directives/ng_control_name.ts @@ -8,7 +8,8 @@ import {forwardRef, Host, SkipSelf, Binding, Inject, Optional} from 'angular2/sr import {ControlContainer} from './control_container'; import {NgControl} from './ng_control'; -import {controlPath, isPropertyUpdated} from './shared'; +import {ControlValueAccessor, NG_VALUE_ACCESSOR} from './control_value_accessor'; +import {controlPath, isPropertyUpdated, selectValueAccessor} from './shared'; import {Control} from '../model'; import {Validators, NG_VALIDATORS} from '../validators'; @@ -88,10 +89,12 @@ export class NgControlName extends NgControl implements OnChanges, _added = false; constructor(@Host() @SkipSelf() parent: ControlContainer, - @Optional() @Inject(NG_VALIDATORS) validators: Function[]) { + @Optional() @Inject(NG_VALIDATORS) validators: Function[], + @Optional() @Inject(NG_VALUE_ACCESSOR) valueAccessors: ControlValueAccessor[]) { super(); this._parent = parent; this.validators = validators; + this.valueAccessor = selectValueAccessor(this, valueAccessors); } onChanges(changes: StringMap) { diff --git a/modules/angular2/src/core/forms/directives/ng_control_status.ts b/modules/angular2/src/core/forms/directives/ng_control_status.ts index 29af72fb2f..1f1712f4a7 100644 --- a/modules/angular2/src/core/forms/directives/ng_control_status.ts +++ b/modules/angular2/src/core/forms/directives/ng_control_status.ts @@ -4,7 +4,7 @@ import {NgControl} from './ng_control'; import {isBlank, isPresent} from 'angular2/src/core/facade/lang'; @Directive({ - selector:'[ng-control],[ng-model],[ng-form-control]', + selector: '[ng-control],[ng-model],[ng-form-control]', host: { '[class.ng-untouched]': 'ngClassUntouched', '[class.ng-touched]': 'ngClassTouched', diff --git a/modules/angular2/src/core/forms/directives/ng_form_control.ts b/modules/angular2/src/core/forms/directives/ng_form_control.ts index 33688235fb..0a311aae75 100644 --- a/modules/angular2/src/core/forms/directives/ng_form_control.ts +++ b/modules/angular2/src/core/forms/directives/ng_form_control.ts @@ -7,7 +7,8 @@ import {forwardRef, Binding, Inject, Optional} from 'angular2/src/core/di'; import {NgControl} from './ng_control'; import {Control} from '../model'; import {Validators, NG_VALIDATORS} from '../validators'; -import {setUpControl, isPropertyUpdated} from './shared'; +import {ControlValueAccessor, NG_VALUE_ACCESSOR} from './control_value_accessor'; +import {setUpControl, isPropertyUpdated, selectValueAccessor} from './shared'; const formControlBinding = CONST_EXPR(new Binding(NgControl, {toAlias: forwardRef(() => NgFormControl)})); @@ -76,9 +77,11 @@ export class NgFormControl extends NgControl implements OnChanges { viewModel: any; validators: Function[]; - constructor(@Optional() @Inject(NG_VALIDATORS) validators: Function[]) { + constructor(@Optional() @Inject(NG_VALIDATORS) validators: Function[], + @Optional() @Inject(NG_VALUE_ACCESSOR) valueAccessors: ControlValueAccessor[]) { super(); this.validators = validators; + this.valueAccessor = selectValueAccessor(this, valueAccessors); } onChanges(changes: StringMap): void { diff --git a/modules/angular2/src/core/forms/directives/ng_model.ts b/modules/angular2/src/core/forms/directives/ng_model.ts index ae0cc8dc7b..d89df4442b 100644 --- a/modules/angular2/src/core/forms/directives/ng_model.ts +++ b/modules/angular2/src/core/forms/directives/ng_model.ts @@ -4,10 +4,11 @@ import {OnChanges} from 'angular2/lifecycle_hooks'; import {SimpleChange} from 'angular2/src/core/change_detection'; import {Query, Directive} from 'angular2/src/core/metadata'; import {forwardRef, Binding, Inject, Optional} from 'angular2/src/core/di'; +import {ControlValueAccessor, NG_VALUE_ACCESSOR} from './control_value_accessor'; import {NgControl} from './ng_control'; import {Control} from '../model'; import {Validators, NG_VALIDATORS} from '../validators'; -import {setUpControl, isPropertyUpdated} from './shared'; +import {setUpControl, isPropertyUpdated, selectValueAccessor} from './shared'; const formControlBinding = CONST_EXPR(new Binding(NgControl, {toAlias: forwardRef(() => NgModel)})); @@ -47,9 +48,11 @@ export class NgModel extends NgControl implements OnChanges { viewModel: any; validators: Function[]; - constructor(@Optional() @Inject(NG_VALIDATORS) validators: Function[]) { + constructor(@Optional() @Inject(NG_VALIDATORS) validators: Function[], + @Optional() @Inject(NG_VALUE_ACCESSOR) valueAccessors: ControlValueAccessor[]) { super(); this.validators = validators; + this.valueAccessor = selectValueAccessor(this, valueAccessors); } onChanges(changes: StringMap) { diff --git a/modules/angular2/src/core/forms/directives/select_control_value_accessor.ts b/modules/angular2/src/core/forms/directives/select_control_value_accessor.ts index 6298857bdf..2989e58b81 100644 --- a/modules/angular2/src/core/forms/directives/select_control_value_accessor.ts +++ b/modules/angular2/src/core/forms/directives/select_control_value_accessor.ts @@ -1,14 +1,16 @@ -import {Self} from 'angular2/src/core/di'; +import {Self, forwardRef, Binding} from 'angular2/src/core/di'; import {Renderer} from 'angular2/src/core/render'; import {ElementRef, QueryList} from 'angular2/src/core/compiler'; import {Query, Directive} from 'angular2/src/core/metadata'; -import {NgControl} from './ng_control'; -import {ControlValueAccessor} from './control_value_accessor'; -import {isPresent} from 'angular2/src/core/facade/lang'; import {ObservableWrapper} from 'angular2/src/core/facade/async'; +import {NG_VALUE_ACCESSOR, ControlValueAccessor} from './control_value_accessor'; +import {CONST_EXPR} from 'angular2/src/core/facade/lang'; import {setProperty} from './shared'; +const SELECT_VALUE_ACCESSOR = CONST_EXPR(new Binding( + NG_VALUE_ACCESSOR, {toAlias: forwardRef(() => SelectControlValueAccessor), multi: true})); + /** * Marks `