feat(forms): make NgControl -> NgValueAccessor dependency unidirectional
Closes #4421
This commit is contained in:
@ -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,
|
||||
|
@ -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; }
|
||||
}
|
||||
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
export const NG_VALUE_ACCESSOR: OpaqueToken = CONST_EXPR(new OpaqueToken("NgValueAccessor"));
|
@ -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;
|
||||
|
@ -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<string, SimpleChange>) {
|
||||
|
@ -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',
|
||||
|
@ -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<string, SimpleChange>): void {
|
||||
|
@ -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<string, SimpleChange>) {
|
||||
|
@ -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 `<option>` as dynamic, so Angular can be notified when options change.
|
||||
*
|
||||
@ -33,16 +35,16 @@ export class NgSelectOption {
|
||||
'(change)': 'onChange($event.target.value)',
|
||||
'(input)': 'onChange($event.target.value)',
|
||||
'(blur)': 'onTouched()'
|
||||
}
|
||||
},
|
||||
bindings: [SELECT_VALUE_ACCESSOR]
|
||||
})
|
||||
export class SelectControlValueAccessor implements ControlValueAccessor {
|
||||
value: string;
|
||||
onChange = (_) => {};
|
||||
onTouched = () => {};
|
||||
|
||||
constructor(@Self() cd: NgControl, private _renderer: Renderer, private _elementRef: ElementRef,
|
||||
constructor(private _renderer: Renderer, private _elementRef: ElementRef,
|
||||
@Query(NgSelectOption, {descendants: true}) query: QueryList<NgSelectOption>) {
|
||||
cd.valueAccessor = this;
|
||||
this._updateValueWhenListOfOptionsChanges(query);
|
||||
}
|
||||
|
||||
|
@ -1,13 +1,17 @@
|
||||
import {ListWrapper, StringMapWrapper} from 'angular2/src/core/facade/collection';
|
||||
import {isBlank, looseIdentical} from 'angular2/src/core/facade/lang';
|
||||
import {isBlank, isPresent, looseIdentical} from 'angular2/src/core/facade/lang';
|
||||
import {BaseException, WrappedException} from 'angular2/src/core/facade/exceptions';
|
||||
|
||||
import {ControlContainer} from './control_container';
|
||||
import {NgControl} from './ng_control';
|
||||
import {Control} from '../model';
|
||||
import {Validators} from '../validators';
|
||||
import {ControlValueAccessor} from './control_value_accessor';
|
||||
import {ElementRef, QueryList} from 'angular2/src/core/compiler';
|
||||
import {Renderer} from 'angular2/src/core/render';
|
||||
import {DefaultValueAccessor} from './default_value_accessor';
|
||||
import {CheckboxControlValueAccessor} from './checkbox_value_accessor';
|
||||
import {SelectControlValueAccessor} from './select_control_value_accessor';
|
||||
|
||||
|
||||
export function controlPath(name: string, parent: ControlContainer): string[] {
|
||||
@ -54,3 +58,37 @@ export function isPropertyUpdated(changes: StringMap<string, any>, viewModel: an
|
||||
if (change.isFirstChange()) return true;
|
||||
return !looseIdentical(viewModel, change.currentValue);
|
||||
}
|
||||
|
||||
// TODO: vsavkin remove it once https://github.com/angular/angular/issues/3011 is implemented
|
||||
export function selectValueAccessor(dir: NgControl, valueAccessors: ControlValueAccessor[]):
|
||||
ControlValueAccessor {
|
||||
if (isBlank(valueAccessors)) return null;
|
||||
|
||||
var defaultAccessor;
|
||||
var builtinAccessor;
|
||||
var customAccessor;
|
||||
|
||||
valueAccessors.forEach(v => {
|
||||
if (v instanceof DefaultValueAccessor) {
|
||||
defaultAccessor = v;
|
||||
|
||||
} else if (v instanceof CheckboxControlValueAccessor ||
|
||||
v instanceof SelectControlValueAccessor) {
|
||||
if (isPresent(builtinAccessor))
|
||||
_throwError(dir, "More than one built-in value accessor matches");
|
||||
builtinAccessor = v;
|
||||
|
||||
} else {
|
||||
if (isPresent(customAccessor))
|
||||
_throwError(dir, "More than one custom value accessor matches");
|
||||
customAccessor = v;
|
||||
}
|
||||
});
|
||||
|
||||
if (isPresent(customAccessor)) return customAccessor;
|
||||
if (isPresent(builtinAccessor)) return builtinAccessor;
|
||||
if (isPresent(defaultAccessor)) return defaultAccessor;
|
||||
|
||||
_throwError(dir, "No valid value accessor for");
|
||||
return null;
|
||||
}
|
Reference in New Issue
Block a user