
- Introduce `InjectionToken<T>` which is a parameterized and type-safe version of `OpaqueToken`. DEPRECATION: - `OpaqueToken` is now deprecated, use `InjectionToken<T>` instead. - `Injector.get(token: any, notFoundValue?: any): any` is now deprecated use the same method which is now overloaded as `Injector.get<T>(token: Type<T>|InjectionToken<T>, notFoundValue?: T): T;`. Migration - Replace `OpaqueToken` with `InjectionToken<?>` and parameterize it. - Migrate your code to only use `Type<?>` or `InjectionToken<?>` as injection tokens. Using other tokens will not be supported in the future. BREAKING CHANGE: - Because `injector.get()` is now parameterize it is possible that code which used to work no longer type checks. Example would be if one injects `Foo` but configures it as `{provide: Foo, useClass: MockFoo}`. The injection instance will be that of `MockFoo` but the type will be `Foo` instead of `any` as in the past. This means that it was possible to call a method on `MockFoo` in the past which now will fail type check. See this example: ``` class Foo {} class MockFoo extends Foo { setupMock(); } var PROVIDERS = [ {provide: Foo, useClass: MockFoo} ]; ... function myTest(injector: Injector) { var foo = injector.get(Foo); // This line used to work since `foo` used to be `any` before this // change, it will now be `Foo`, and `Foo` does not have `setUpMock()`. // The fix is to downcast: `injector.get(Foo) as MockFoo`. foo.setUpMock(); } ``` PR Close #13785
180 lines
6.7 KiB
TypeScript
180 lines
6.7 KiB
TypeScript
/**
|
|
* @license
|
|
* Copyright Google Inc. All Rights Reserved.
|
|
*
|
|
* Use of this source code is governed by an MIT-style license that can be
|
|
* found in the LICENSE file at https://angular.io/license
|
|
*/
|
|
|
|
import {isBlank, isPresent, looseIdentical} from '../facade/lang';
|
|
import {FormArray, FormControl, FormGroup} from '../model';
|
|
import {Validators} from '../validators';
|
|
|
|
import {AbstractControlDirective} from './abstract_control_directive';
|
|
import {AbstractFormGroupDirective} from './abstract_form_group_directive';
|
|
import {CheckboxControlValueAccessor} from './checkbox_value_accessor';
|
|
import {ControlContainer} from './control_container';
|
|
import {ControlValueAccessor} from './control_value_accessor';
|
|
import {DefaultValueAccessor} from './default_value_accessor';
|
|
import {NgControl} from './ng_control';
|
|
import {normalizeAsyncValidator, normalizeValidator} from './normalize_validator';
|
|
import {NumberValueAccessor} from './number_value_accessor';
|
|
import {RadioControlValueAccessor} from './radio_control_value_accessor';
|
|
import {RangeValueAccessor} from './range_value_accessor';
|
|
import {FormArrayName} from './reactive_directives/form_group_name';
|
|
import {SelectControlValueAccessor} from './select_control_value_accessor';
|
|
import {SelectMultipleControlValueAccessor} from './select_multiple_control_value_accessor';
|
|
import {AsyncValidatorFn, Validator, ValidatorFn} from './validators';
|
|
|
|
|
|
export function controlPath(name: string, parent: ControlContainer): string[] {
|
|
return [...parent.path, name];
|
|
}
|
|
|
|
export function setUpControl(control: FormControl, dir: NgControl): void {
|
|
if (!control) _throwError(dir, 'Cannot find control with');
|
|
if (!dir.valueAccessor) _throwError(dir, 'No value accessor for form control with');
|
|
|
|
control.validator = Validators.compose([control.validator, dir.validator]);
|
|
control.asyncValidator = Validators.composeAsync([control.asyncValidator, dir.asyncValidator]);
|
|
dir.valueAccessor.writeValue(control.value);
|
|
|
|
// view -> model
|
|
dir.valueAccessor.registerOnChange((newValue: any) => {
|
|
dir.viewToModelUpdate(newValue);
|
|
control.markAsDirty();
|
|
control.setValue(newValue, {emitModelToViewChange: false});
|
|
});
|
|
|
|
// touched
|
|
dir.valueAccessor.registerOnTouched(() => control.markAsTouched());
|
|
|
|
control.registerOnChange((newValue: any, emitModelEvent: boolean) => {
|
|
// control -> view
|
|
dir.valueAccessor.writeValue(newValue);
|
|
|
|
// control -> ngModel
|
|
if (emitModelEvent) dir.viewToModelUpdate(newValue);
|
|
});
|
|
|
|
if (dir.valueAccessor.setDisabledState) {
|
|
control.registerOnDisabledChange(
|
|
(isDisabled: boolean) => { dir.valueAccessor.setDisabledState(isDisabled); });
|
|
}
|
|
|
|
// re-run validation when validator binding changes, e.g. minlength=3 -> minlength=4
|
|
dir._rawValidators.forEach((validator: Validator | ValidatorFn) => {
|
|
if ((<Validator>validator).registerOnValidatorChange)
|
|
(<Validator>validator).registerOnValidatorChange(() => control.updateValueAndValidity());
|
|
});
|
|
|
|
dir._rawAsyncValidators.forEach((validator: Validator | ValidatorFn) => {
|
|
if ((<Validator>validator).registerOnValidatorChange)
|
|
(<Validator>validator).registerOnValidatorChange(() => control.updateValueAndValidity());
|
|
});
|
|
}
|
|
|
|
export function cleanUpControl(control: FormControl, dir: NgControl) {
|
|
dir.valueAccessor.registerOnChange(() => _noControlError(dir));
|
|
dir.valueAccessor.registerOnTouched(() => _noControlError(dir));
|
|
|
|
dir._rawValidators.forEach((validator: any) => {
|
|
if (validator.registerOnValidatorChange) {
|
|
validator.registerOnValidatorChange(null);
|
|
}
|
|
});
|
|
|
|
dir._rawAsyncValidators.forEach((validator: any) => {
|
|
if (validator.registerOnValidatorChange) {
|
|
validator.registerOnValidatorChange(null);
|
|
}
|
|
});
|
|
|
|
if (control) control._clearChangeFns();
|
|
}
|
|
|
|
export function setUpFormContainer(
|
|
control: FormGroup | FormArray, dir: AbstractFormGroupDirective | FormArrayName) {
|
|
if (isBlank(control)) _throwError(dir, 'Cannot find control with');
|
|
control.validator = Validators.compose([control.validator, dir.validator]);
|
|
control.asyncValidator = Validators.composeAsync([control.asyncValidator, dir.asyncValidator]);
|
|
}
|
|
|
|
function _noControlError(dir: NgControl) {
|
|
return _throwError(dir, 'There is no FormControl instance attached to form control element with');
|
|
}
|
|
|
|
function _throwError(dir: AbstractControlDirective, message: string): void {
|
|
let messageEnd: string;
|
|
if (dir.path.length > 1) {
|
|
messageEnd = `path: '${dir.path.join(' -> ')}'`;
|
|
} else if (dir.path[0]) {
|
|
messageEnd = `name: '${dir.path}'`;
|
|
} else {
|
|
messageEnd = 'unspecified name attribute';
|
|
}
|
|
throw new Error(`${message} ${messageEnd}`);
|
|
}
|
|
|
|
export function composeValidators(validators: Array<Validator|Function>): ValidatorFn {
|
|
return isPresent(validators) ? Validators.compose(validators.map(normalizeValidator)) : null;
|
|
}
|
|
|
|
export function composeAsyncValidators(validators: Array<Validator|Function>): AsyncValidatorFn {
|
|
return isPresent(validators) ? Validators.composeAsync(validators.map(normalizeAsyncValidator)) :
|
|
null;
|
|
}
|
|
|
|
export function isPropertyUpdated(changes: {[key: string]: any}, viewModel: any): boolean {
|
|
if (!changes.hasOwnProperty('model')) return false;
|
|
const change = changes['model'];
|
|
|
|
if (change.isFirstChange()) return true;
|
|
return !looseIdentical(viewModel, change.currentValue);
|
|
}
|
|
|
|
const BUILTIN_ACCESSORS = [
|
|
CheckboxControlValueAccessor,
|
|
RangeValueAccessor,
|
|
NumberValueAccessor,
|
|
SelectControlValueAccessor,
|
|
SelectMultipleControlValueAccessor,
|
|
RadioControlValueAccessor,
|
|
];
|
|
|
|
export function isBuiltInAccessor(valueAccessor: ControlValueAccessor): boolean {
|
|
return BUILTIN_ACCESSORS.some(a => valueAccessor.constructor === a);
|
|
}
|
|
|
|
// TODO: vsavkin remove it once https://github.com/angular/angular/issues/3011 is implemented
|
|
export function selectValueAccessor(
|
|
dir: NgControl, valueAccessors: ControlValueAccessor[]): ControlValueAccessor {
|
|
if (!valueAccessors) return null;
|
|
|
|
let defaultAccessor: ControlValueAccessor;
|
|
let builtinAccessor: ControlValueAccessor;
|
|
let customAccessor: ControlValueAccessor;
|
|
valueAccessors.forEach((v: ControlValueAccessor) => {
|
|
if (v.constructor === DefaultValueAccessor) {
|
|
defaultAccessor = v;
|
|
|
|
} else if (isBuiltInAccessor(v)) {
|
|
if (builtinAccessor)
|
|
_throwError(dir, 'More than one built-in value accessor matches form control with');
|
|
builtinAccessor = v;
|
|
|
|
} else {
|
|
if (customAccessor)
|
|
_throwError(dir, 'More than one custom value accessor matches form control with');
|
|
customAccessor = v;
|
|
}
|
|
});
|
|
|
|
if (customAccessor) return customAccessor;
|
|
if (builtinAccessor) return builtinAccessor;
|
|
if (defaultAccessor) return defaultAccessor;
|
|
|
|
_throwError(dir, 'No valid value accessor for form control with');
|
|
return null;
|
|
}
|