feat(forms): add support for disabled controls (#10994)
This commit is contained in:
@@ -41,6 +41,10 @@ export abstract class AbstractControlDirective {
|
||||
|
||||
get untouched(): boolean { return isPresent(this.control) ? this.control.untouched : null; }
|
||||
|
||||
get disabled(): boolean { return isPresent(this.control) ? this.control.disabled : null; }
|
||||
|
||||
get enabled(): boolean { return isPresent(this.control) ? this.control.enabled : null; }
|
||||
|
||||
get statusChanges(): Observable<any> {
|
||||
return isPresent(this.control) ? this.control.statusChanges : null;
|
||||
}
|
||||
|
||||
@@ -43,4 +43,8 @@ export class CheckboxControlValueAccessor implements ControlValueAccessor {
|
||||
}
|
||||
registerOnChange(fn: (_: any) => {}): void { this.onChange = fn; }
|
||||
registerOnTouched(fn: () => {}): void { this.onTouched = fn; }
|
||||
|
||||
setDisabledState(isDisabled: boolean): void {
|
||||
this._renderer.setElementProperty(this._elementRef.nativeElement, 'disabled', isDisabled);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -33,6 +33,14 @@ export interface ControlValueAccessor {
|
||||
* Set the function to be called when the control receives a touch event.
|
||||
*/
|
||||
registerOnTouched(fn: any): void;
|
||||
|
||||
/**
|
||||
* This function is called when the control status changes to or from "DISABLED".
|
||||
* Depending on the value, it will enable or disable the appropriate DOM element.
|
||||
*
|
||||
* @param isDisabled
|
||||
*/
|
||||
setDisabledState?(isDisabled: boolean): void;
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -51,4 +51,8 @@ export class DefaultValueAccessor implements ControlValueAccessor {
|
||||
|
||||
registerOnChange(fn: (_: any) => void): void { this.onChange = fn; }
|
||||
registerOnTouched(fn: () => void): void { this.onTouched = fn; }
|
||||
|
||||
setDisabledState(isDisabled: boolean): void {
|
||||
this._renderer.setElementProperty(this._elementRef.nativeElement, 'disabled', isDisabled);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -104,8 +104,8 @@ export class NgForm extends ControlContainer implements Form {
|
||||
@Optional() @Self() @Inject(NG_VALIDATORS) validators: any[],
|
||||
@Optional() @Self() @Inject(NG_ASYNC_VALIDATORS) asyncValidators: any[]) {
|
||||
super();
|
||||
this.form = new FormGroup(
|
||||
{}, null, composeValidators(validators), composeAsyncValidators(asyncValidators));
|
||||
this.form =
|
||||
new FormGroup({}, composeValidators(validators), composeAsyncValidators(asyncValidators));
|
||||
}
|
||||
|
||||
get submitted(): boolean { return this._submitted; }
|
||||
|
||||
@@ -64,9 +64,11 @@ export class NgModel extends NgControl implements OnChanges,
|
||||
_registered = false;
|
||||
viewModel: any;
|
||||
|
||||
@Input('ngModel') model: any;
|
||||
@Input() name: string;
|
||||
@Input() disabled: boolean;
|
||||
@Input('ngModel') model: any;
|
||||
@Input('ngModelOptions') options: {name?: string, standalone?: boolean};
|
||||
|
||||
@Output('ngModelChange') update = new EventEmitter();
|
||||
|
||||
constructor(@Optional() @Host() private _parent: ControlContainer,
|
||||
@@ -81,6 +83,9 @@ export class NgModel extends NgControl implements OnChanges,
|
||||
ngOnChanges(changes: SimpleChanges) {
|
||||
this._checkForErrors();
|
||||
if (!this._registered) this._setUpControl();
|
||||
if ('disabled' in changes) {
|
||||
this._updateDisabled(changes);
|
||||
}
|
||||
|
||||
if (isPropertyUpdated(changes, this.viewModel)) {
|
||||
this._updateValue(this.model);
|
||||
@@ -153,4 +158,17 @@ export class NgModel extends NgControl implements OnChanges,
|
||||
resolvedPromise.then(
|
||||
() => { this.control.setValue(value, {emitViewToModelChange: false}); });
|
||||
}
|
||||
|
||||
private _updateDisabled(changes: SimpleChanges) {
|
||||
const disabledValue = changes['disabled'].currentValue;
|
||||
const isDisabled = disabledValue != null && disabledValue != false;
|
||||
|
||||
resolvedPromise.then(() => {
|
||||
if (isDisabled && !this.control.disabled) {
|
||||
this.control.disable();
|
||||
} else if (!isDisabled && this.control.disabled) {
|
||||
this.control.enable();
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@@ -53,4 +53,8 @@ export class NumberValueAccessor implements ControlValueAccessor {
|
||||
this.onChange = (value) => { fn(value == '' ? null : NumberWrapper.parseFloat(value)); };
|
||||
}
|
||||
registerOnTouched(fn: () => void): void { this.onTouched = fn; }
|
||||
|
||||
setDisabledState(isDisabled: boolean): void {
|
||||
this._renderer.setElementProperty(this._elementRef.nativeElement, 'disabled', isDisabled);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -127,6 +127,10 @@ export class RadioControlValueAccessor implements ControlValueAccessor,
|
||||
|
||||
registerOnTouched(fn: () => {}): void { this.onTouched = fn; }
|
||||
|
||||
setDisabledState(isDisabled: boolean): void {
|
||||
this._renderer.setElementProperty(this._elementRef.nativeElement, 'disabled', isDisabled);
|
||||
}
|
||||
|
||||
private _checkName(): void {
|
||||
if (this.name && this.formControlName && this.name !== this.formControlName) {
|
||||
this._throwNameError();
|
||||
|
||||
@@ -12,9 +12,9 @@ import {EventEmitter} from '../../facade/async';
|
||||
import {StringMapWrapper} from '../../facade/collection';
|
||||
import {FormControl} from '../../model';
|
||||
import {NG_ASYNC_VALIDATORS, NG_VALIDATORS} from '../../validators';
|
||||
|
||||
import {ControlValueAccessor, NG_VALUE_ACCESSOR} from '../control_value_accessor';
|
||||
import {NgControl} from '../ng_control';
|
||||
import {ReactiveErrors} from '../reactive_errors';
|
||||
import {composeAsyncValidators, composeValidators, isPropertyUpdated, selectValueAccessor, setUpControl} from '../shared';
|
||||
import {AsyncValidatorFn, ValidatorFn} from '../validators';
|
||||
|
||||
@@ -81,6 +81,9 @@ export class FormControlDirective extends NgControl implements OnChanges {
|
||||
@Input('ngModel') model: any;
|
||||
@Output('ngModelChange') update = new EventEmitter();
|
||||
|
||||
@Input('disabled')
|
||||
set disabled(isDisabled: boolean) { ReactiveErrors.disabledAttrWarning(); }
|
||||
|
||||
constructor(@Optional() @Self() @Inject(NG_VALIDATORS) private _validators:
|
||||
/* Array<Validator|Function> */ any[],
|
||||
@Optional() @Self() @Inject(NG_ASYNC_VALIDATORS) private _asyncValidators:
|
||||
@@ -94,6 +97,7 @@ export class FormControlDirective extends NgControl implements OnChanges {
|
||||
ngOnChanges(changes: SimpleChanges): void {
|
||||
if (this._isControlChanged(changes)) {
|
||||
setUpControl(this.form, this);
|
||||
if (this.control.disabled) this.valueAccessor.setDisabledState(true);
|
||||
this.form.updateValueAndValidity({emitEvent: false});
|
||||
}
|
||||
if (isPropertyUpdated(changes, this.viewModel)) {
|
||||
|
||||
@@ -106,57 +106,58 @@ export class FormControlName extends NgControl implements OnChanges, OnDestroy {
|
||||
@Input('ngModel') model: any;
|
||||
@Output('ngModelChange') update = new EventEmitter();
|
||||
|
||||
constructor(@Optional() @Host() @SkipSelf() private _parent: ControlContainer,
|
||||
@Optional() @Self() @Inject(NG_VALIDATORS) private _validators:
|
||||
/* Array<Validator|Function> */ any[],
|
||||
@Optional() @Self() @Inject(NG_ASYNC_VALIDATORS) private _asyncValidators:
|
||||
/* Array<Validator|Function> */ any[],
|
||||
@Optional() @Self() @Inject(NG_VALUE_ACCESSOR)
|
||||
valueAccessors: ControlValueAccessor[]) {
|
||||
super();
|
||||
this.valueAccessor = selectValueAccessor(this, valueAccessors);
|
||||
}
|
||||
@Input('disabled')
|
||||
set disabled(isDisabled: boolean) { ReactiveErrors.disabledAttrWarning(); }
|
||||
|
||||
ngOnChanges(changes: SimpleChanges) {
|
||||
if (!this._added) {
|
||||
this._checkParentType();
|
||||
this.formDirective.addControl(this);
|
||||
this._added = true;
|
||||
}
|
||||
if (isPropertyUpdated(changes, this.viewModel)) {
|
||||
this.viewModel = this.model;
|
||||
this.formDirective.updateModel(this, this.model);
|
||||
}
|
||||
}
|
||||
constructor(
|
||||
@Optional() @Host() @SkipSelf() private _parent: ControlContainer,
|
||||
@Optional() @Self() @Inject(NG_VALIDATORS) private _validators:
|
||||
/* Array<Validator|Function> */ any[],
|
||||
@Optional() @Self() @Inject(NG_ASYNC_VALIDATORS) private _asyncValidators:
|
||||
/* Array<Validator|Function> */ any[],
|
||||
@Optional() @Self() @Inject(NG_VALUE_ACCESSOR) valueAccessors: ControlValueAccessor[]) {
|
||||
super();
|
||||
this.valueAccessor = selectValueAccessor(this, valueAccessors);
|
||||
}
|
||||
|
||||
ngOnDestroy(): void { this.formDirective.removeControl(this); }
|
||||
ngOnChanges(changes: SimpleChanges) {
|
||||
if (!this._added) {
|
||||
this._checkParentType();
|
||||
this.formDirective.addControl(this);
|
||||
if (this.control.disabled) this.valueAccessor.setDisabledState(true);
|
||||
this._added = true;
|
||||
}
|
||||
if (isPropertyUpdated(changes, this.viewModel)) {
|
||||
this.viewModel = this.model;
|
||||
this.formDirective.updateModel(this, this.model);
|
||||
}
|
||||
}
|
||||
|
||||
viewToModelUpdate(newValue: any): void {
|
||||
this.viewModel = newValue;
|
||||
this.update.emit(newValue);
|
||||
}
|
||||
ngOnDestroy(): void { this.formDirective.removeControl(this); }
|
||||
|
||||
get path(): string[] { return controlPath(this.name, this._parent); }
|
||||
viewToModelUpdate(newValue: any): void {
|
||||
this.viewModel = newValue;
|
||||
this.update.emit(newValue);
|
||||
}
|
||||
|
||||
get formDirective(): any { return this._parent.formDirective; }
|
||||
get path(): string[] { return controlPath(this.name, this._parent); }
|
||||
|
||||
get validator(): ValidatorFn { return composeValidators(this._validators); }
|
||||
get formDirective(): any { return this._parent.formDirective; }
|
||||
|
||||
get asyncValidator(): AsyncValidatorFn {
|
||||
return composeAsyncValidators(this._asyncValidators);
|
||||
}
|
||||
get validator(): ValidatorFn { return composeValidators(this._validators); }
|
||||
|
||||
get control(): FormControl { return this.formDirective.getControl(this); }
|
||||
get asyncValidator(): AsyncValidatorFn { return composeAsyncValidators(this._asyncValidators); }
|
||||
|
||||
private _checkParentType(): void {
|
||||
if (!(this._parent instanceof FormGroupName) &&
|
||||
this._parent instanceof AbstractFormGroupDirective) {
|
||||
ReactiveErrors.ngModelGroupException();
|
||||
} else if (
|
||||
!(this._parent instanceof FormGroupName) &&
|
||||
!(this._parent instanceof FormGroupDirective) &&
|
||||
!(this._parent instanceof FormArrayName)) {
|
||||
ReactiveErrors.controlParentException();
|
||||
}
|
||||
}
|
||||
get control(): FormControl { return this.formDirective.getControl(this); }
|
||||
|
||||
private _checkParentType(): void {
|
||||
if (!(this._parent instanceof FormGroupName) &&
|
||||
this._parent instanceof AbstractFormGroupDirective) {
|
||||
ReactiveErrors.ngModelGroupException();
|
||||
} else if (
|
||||
!(this._parent instanceof FormGroupName) && !(this._parent instanceof FormGroupDirective) &&
|
||||
!(this._parent instanceof FormArrayName)) {
|
||||
ReactiveErrors.controlParentException();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -61,4 +61,18 @@ export class ReactiveErrors {
|
||||
|
||||
${Examples.formArrayName}`);
|
||||
}
|
||||
|
||||
static disabledAttrWarning(): void {
|
||||
console.warn(`
|
||||
It looks like you're using the disabled attribute with a reactive form directive. If you set disabled to true
|
||||
when you set up this control in your component class, the disabled attribute will actually be set in the DOM for
|
||||
you. We recommend using this approach to avoid 'changed after checked' errors.
|
||||
|
||||
Example:
|
||||
form = new FormGroup({
|
||||
first: new FormControl({value: 'Nancy', disabled: true}, Validators.required),
|
||||
last: new FormControl('Drew', Validators.required)
|
||||
});
|
||||
`);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -71,6 +71,10 @@ export class SelectControlValueAccessor implements ControlValueAccessor {
|
||||
}
|
||||
registerOnTouched(fn: () => any): void { this.onTouched = fn; }
|
||||
|
||||
setDisabledState(isDisabled: boolean): void {
|
||||
this._renderer.setElementProperty(this._elementRef.nativeElement, 'disabled', isDisabled);
|
||||
}
|
||||
|
||||
/** @internal */
|
||||
_registerOption(): string { return (this._idCounter++).toString(); }
|
||||
|
||||
|
||||
@@ -63,7 +63,7 @@ export class SelectMultipleControlValueAccessor implements ControlValueAccessor
|
||||
onChange = (_: any) => {};
|
||||
onTouched = () => {};
|
||||
|
||||
constructor() {}
|
||||
constructor(private _renderer: Renderer, private _elementRef: ElementRef) {}
|
||||
|
||||
writeValue(value: any): void {
|
||||
this.value = value;
|
||||
@@ -101,6 +101,10 @@ export class SelectMultipleControlValueAccessor implements ControlValueAccessor
|
||||
}
|
||||
registerOnTouched(fn: () => any): void { this.onTouched = fn; }
|
||||
|
||||
setDisabledState(isDisabled: boolean): void {
|
||||
this._renderer.setElementProperty(this._elementRef.nativeElement, 'disabled', isDisabled);
|
||||
}
|
||||
|
||||
/** @internal */
|
||||
_registerOption(value: NgSelectMultipleOption): string {
|
||||
let id: string = (this._idCounter++).toString();
|
||||
|
||||
@@ -58,6 +58,11 @@ export function setUpControl(control: FormControl, dir: NgControl): void {
|
||||
if (emitModelEvent) dir.viewToModelUpdate(newValue);
|
||||
});
|
||||
|
||||
if (dir.valueAccessor.setDisabledState) {
|
||||
control.registerOnDisabledChange(
|
||||
(isDisabled: boolean) => { dir.valueAccessor.setDisabledState(isDisabled); });
|
||||
}
|
||||
|
||||
// touched
|
||||
dir.valueAccessor.registerOnTouched(() => control.markAsTouched());
|
||||
}
|
||||
@@ -99,6 +104,15 @@ export function isPropertyUpdated(changes: {[key: string]: any}, viewModel: any)
|
||||
return !looseIdentical(viewModel, change.currentValue);
|
||||
}
|
||||
|
||||
export function isBuiltInAccessor(valueAccessor: ControlValueAccessor): boolean {
|
||||
return (
|
||||
hasConstructor(valueAccessor, CheckboxControlValueAccessor) ||
|
||||
hasConstructor(valueAccessor, NumberValueAccessor) ||
|
||||
hasConstructor(valueAccessor, SelectControlValueAccessor) ||
|
||||
hasConstructor(valueAccessor, SelectMultipleControlValueAccessor) ||
|
||||
hasConstructor(valueAccessor, RadioControlValueAccessor));
|
||||
}
|
||||
|
||||
// TODO: vsavkin remove it once https://github.com/angular/angular/issues/3011 is implemented
|
||||
export function selectValueAccessor(
|
||||
dir: NgControl, valueAccessors: ControlValueAccessor[]): ControlValueAccessor {
|
||||
@@ -111,11 +125,7 @@ export function selectValueAccessor(
|
||||
if (hasConstructor(v, DefaultValueAccessor)) {
|
||||
defaultAccessor = v;
|
||||
|
||||
} else if (
|
||||
hasConstructor(v, CheckboxControlValueAccessor) || hasConstructor(v, NumberValueAccessor) ||
|
||||
hasConstructor(v, SelectControlValueAccessor) ||
|
||||
hasConstructor(v, SelectMultipleControlValueAccessor) ||
|
||||
hasConstructor(v, RadioControlValueAccessor)) {
|
||||
} else if (isBuiltInAccessor(v)) {
|
||||
if (isPresent(builtinAccessor))
|
||||
_throwError(dir, 'More than one built-in value accessor matches form control with');
|
||||
builtinAccessor = v;
|
||||
|
||||
@@ -64,21 +64,21 @@ export class FormBuilder {
|
||||
* See the {@link FormGroup} constructor for more details.
|
||||
*/
|
||||
group(controlsConfig: {[key: string]: any}, extra: {[key: string]: any} = null): FormGroup {
|
||||
var controls = this._reduceControls(controlsConfig);
|
||||
var optionals = <{[key: string]: boolean}>(
|
||||
isPresent(extra) ? StringMapWrapper.get(extra, 'optionals') : null);
|
||||
var validator: ValidatorFn = isPresent(extra) ? StringMapWrapper.get(extra, 'validator') : null;
|
||||
var asyncValidator: AsyncValidatorFn =
|
||||
const controls = this._reduceControls(controlsConfig);
|
||||
const validator: ValidatorFn =
|
||||
isPresent(extra) ? StringMapWrapper.get(extra, 'validator') : null;
|
||||
const asyncValidator: AsyncValidatorFn =
|
||||
isPresent(extra) ? StringMapWrapper.get(extra, 'asyncValidator') : null;
|
||||
return new FormGroup(controls, optionals, validator, asyncValidator);
|
||||
return new FormGroup(controls, validator, asyncValidator);
|
||||
}
|
||||
/**
|
||||
* Construct a new {@link FormControl} with the given `value`,`validator`, and `asyncValidator`.
|
||||
* Construct a new {@link FormControl} with the given `formState`,`validator`, and
|
||||
* `asyncValidator`.
|
||||
*/
|
||||
control(
|
||||
value: Object, validator: ValidatorFn|ValidatorFn[] = null,
|
||||
formState: Object, validator: ValidatorFn|ValidatorFn[] = null,
|
||||
asyncValidator: AsyncValidatorFn|AsyncValidatorFn[] = null): FormControl {
|
||||
return new FormControl(value, validator, asyncValidator);
|
||||
return new FormControl(formState, validator, asyncValidator);
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -13,7 +13,7 @@ import {composeAsyncValidators, composeValidators} from './directives/shared';
|
||||
import {AsyncValidatorFn, ValidatorFn} from './directives/validators';
|
||||
import {EventEmitter, Observable} from './facade/async';
|
||||
import {ListWrapper, StringMapWrapper} from './facade/collection';
|
||||
import {isBlank, isPresent, isPromise, normalizeBool} from './facade/lang';
|
||||
import {isBlank, isPresent, isPromise, isStringMap, normalizeBool} from './facade/lang';
|
||||
|
||||
|
||||
|
||||
@@ -33,6 +33,12 @@ export const INVALID = 'INVALID';
|
||||
*/
|
||||
export const PENDING = 'PENDING';
|
||||
|
||||
/**
|
||||
* Indicates that a FormControl is disabled, i.e. that the control is exempt from ancestor
|
||||
* calculations of validity or value.
|
||||
*/
|
||||
export const DISABLED = 'DISABLED';
|
||||
|
||||
export function isControl(control: Object): boolean {
|
||||
return control instanceof AbstractControl;
|
||||
}
|
||||
@@ -115,6 +121,10 @@ export abstract class AbstractControl {
|
||||
|
||||
get pending(): boolean { return this._status == PENDING; }
|
||||
|
||||
get disabled(): boolean { return this._status === DISABLED; }
|
||||
|
||||
get enabled(): boolean { return this._status !== DISABLED; }
|
||||
|
||||
setAsyncValidators(newValidator: AsyncValidatorFn|AsyncValidatorFn[]): void {
|
||||
this.asyncValidator = coerceToAsyncValidator(newValidator);
|
||||
}
|
||||
@@ -175,6 +185,39 @@ export abstract class AbstractControl {
|
||||
}
|
||||
}
|
||||
|
||||
disable({onlySelf, emitEvent}: {onlySelf?: boolean, emitEvent?: boolean} = {}): void {
|
||||
emitEvent = isPresent(emitEvent) ? emitEvent : true;
|
||||
|
||||
this._status = DISABLED;
|
||||
this._forEachChild((control: AbstractControl) => { control.disable({onlySelf: true}); });
|
||||
this._updateValue();
|
||||
|
||||
if (emitEvent) {
|
||||
this._valueChanges.emit(this._value);
|
||||
this._statusChanges.emit(this._status);
|
||||
}
|
||||
|
||||
this._updateAncestors(onlySelf);
|
||||
this._onDisabledChange(true);
|
||||
}
|
||||
|
||||
enable({onlySelf, emitEvent}: {onlySelf?: boolean, emitEvent?: boolean} = {}): void {
|
||||
this._status = VALID;
|
||||
this._forEachChild((control: AbstractControl) => { control.enable({onlySelf: true}); });
|
||||
this.updateValueAndValidity({onlySelf: true, emitEvent: emitEvent});
|
||||
|
||||
this._updateAncestors(onlySelf);
|
||||
this._onDisabledChange(false);
|
||||
}
|
||||
|
||||
private _updateAncestors(onlySelf: boolean) {
|
||||
if (isPresent(this._parent) && !onlySelf) {
|
||||
this._parent.updateValueAndValidity();
|
||||
this._parent._updatePristine();
|
||||
this._parent._updateTouched();
|
||||
}
|
||||
}
|
||||
|
||||
setParent(parent: FormGroup|FormArray): void { this._parent = parent; }
|
||||
|
||||
abstract setValue(value: any, options?: Object): void;
|
||||
@@ -189,14 +232,18 @@ export abstract class AbstractControl {
|
||||
emitEvent = isPresent(emitEvent) ? emitEvent : true;
|
||||
|
||||
this._updateValue();
|
||||
|
||||
this._errors = this._runValidator();
|
||||
const originalStatus = this._status;
|
||||
this._status = this._calculateStatus();
|
||||
|
||||
if (this._status == VALID || this._status == PENDING) {
|
||||
this._runAsyncValidator(emitEvent);
|
||||
}
|
||||
|
||||
if (this._disabledChanged(originalStatus)) {
|
||||
this._updateValue();
|
||||
}
|
||||
|
||||
if (emitEvent) {
|
||||
this._valueChanges.emit(this._value);
|
||||
this._statusChanges.emit(this._status);
|
||||
@@ -227,6 +274,11 @@ export abstract class AbstractControl {
|
||||
}
|
||||
}
|
||||
|
||||
private _disabledChanged(originalStatus: string): boolean {
|
||||
return this._status !== originalStatus &&
|
||||
(this._status === DISABLED || originalStatus === DISABLED);
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets errors on a form control.
|
||||
*
|
||||
@@ -306,6 +358,7 @@ export abstract class AbstractControl {
|
||||
if (isPresent(this._errors)) return INVALID;
|
||||
if (this._anyControlsHaveStatus(PENDING)) return PENDING;
|
||||
if (this._anyControlsHaveStatus(INVALID)) return INVALID;
|
||||
if (this._allControlsDisabled()) return DISABLED;
|
||||
return VALID;
|
||||
}
|
||||
|
||||
@@ -318,6 +371,9 @@ export abstract class AbstractControl {
|
||||
/** @internal */
|
||||
abstract _anyControls(condition: Function): boolean;
|
||||
|
||||
/** @internal */
|
||||
abstract _allControlsDisabled(): boolean;
|
||||
|
||||
/** @internal */
|
||||
_anyControlsHaveStatus(status: string): boolean {
|
||||
return this._anyControls((control: AbstractControl) => control.status == status);
|
||||
@@ -350,6 +406,15 @@ export abstract class AbstractControl {
|
||||
this._parent._updateTouched({onlySelf: onlySelf});
|
||||
}
|
||||
}
|
||||
|
||||
/** @internal */
|
||||
_onDisabledChange(isDisabled: boolean): void {}
|
||||
|
||||
/** @internal */
|
||||
_isBoxedValue(formState: any): boolean {
|
||||
return isStringMap(formState) && Object.keys(formState).length === 2 && 'value' in formState &&
|
||||
'disabled' in formState;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -375,10 +440,10 @@ export class FormControl extends AbstractControl {
|
||||
_onChange: Function[] = [];
|
||||
|
||||
constructor(
|
||||
value: any = null, validator: ValidatorFn|ValidatorFn[] = null,
|
||||
formState: any = null, validator: ValidatorFn|ValidatorFn[] = null,
|
||||
asyncValidator: AsyncValidatorFn|AsyncValidatorFn[] = null) {
|
||||
super(coerceToValidator(validator), coerceToAsyncValidator(asyncValidator));
|
||||
this._value = value;
|
||||
this._applyFormState(formState);
|
||||
this.updateValueAndValidity({onlySelf: true, emitEvent: false});
|
||||
this._initObservables();
|
||||
}
|
||||
@@ -427,10 +492,11 @@ export class FormControl extends AbstractControl {
|
||||
this.setValue(value, options);
|
||||
}
|
||||
|
||||
reset(value: any = null, {onlySelf}: {onlySelf?: boolean} = {}): void {
|
||||
this.markAsPristine({onlySelf: onlySelf});
|
||||
this.markAsUntouched({onlySelf: onlySelf});
|
||||
this.setValue(value, {onlySelf: onlySelf});
|
||||
reset(formState: any = null, {onlySelf}: {onlySelf?: boolean} = {}): void {
|
||||
this._applyFormState(formState);
|
||||
this.markAsPristine({onlySelf});
|
||||
this.markAsUntouched({onlySelf});
|
||||
this.setValue(this._value, {onlySelf});
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -443,15 +509,35 @@ export class FormControl extends AbstractControl {
|
||||
*/
|
||||
_anyControls(condition: Function): boolean { return false; }
|
||||
|
||||
/**
|
||||
* @internal
|
||||
*/
|
||||
_allControlsDisabled(): boolean { return this.disabled; }
|
||||
|
||||
/**
|
||||
* Register a listener for change events.
|
||||
*/
|
||||
registerOnChange(fn: Function): void { this._onChange.push(fn); }
|
||||
|
||||
/**
|
||||
* Register a listener for disabled events.
|
||||
*/
|
||||
registerOnDisabledChange(fn: (isDisabled: boolean) => void): void { this._onDisabledChange = fn; }
|
||||
|
||||
/**
|
||||
* @internal
|
||||
*/
|
||||
_forEachChild(cb: Function): void {}
|
||||
|
||||
private _applyFormState(formState: any) {
|
||||
if (this._isBoxedValue(formState)) {
|
||||
this._value = formState.value;
|
||||
formState.disabled ? this.disable({onlySelf: true, emitEvent: false}) :
|
||||
this.enable({onlySelf: true, emitEvent: false});
|
||||
} else {
|
||||
this._value = formState;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -470,14 +556,10 @@ export class FormControl extends AbstractControl {
|
||||
* @stable
|
||||
*/
|
||||
export class FormGroup extends AbstractControl {
|
||||
private _optionals: {[key: string]: boolean};
|
||||
|
||||
constructor(
|
||||
public controls: {[key: string]: AbstractControl},
|
||||
/* @deprecated */ optionals: {[key: string]: boolean} = null, validator: ValidatorFn = null,
|
||||
public controls: {[key: string]: AbstractControl}, validator: ValidatorFn = null,
|
||||
asyncValidator: AsyncValidatorFn = null) {
|
||||
super(validator, asyncValidator);
|
||||
this._optionals = isPresent(optionals) ? optionals : {};
|
||||
this._initObservables();
|
||||
this._setParentForControls();
|
||||
this.updateValueAndValidity({onlySelf: true, emitEvent: false});
|
||||
@@ -509,30 +591,12 @@ export class FormGroup extends AbstractControl {
|
||||
this.updateValueAndValidity();
|
||||
}
|
||||
|
||||
/**
|
||||
* Mark the named control as non-optional.
|
||||
* @deprecated
|
||||
*/
|
||||
include(controlName: string): void {
|
||||
StringMapWrapper.set(this._optionals, controlName, true);
|
||||
this.updateValueAndValidity();
|
||||
}
|
||||
|
||||
/**
|
||||
* Mark the named control as optional.
|
||||
* @deprecated
|
||||
*/
|
||||
exclude(controlName: string): void {
|
||||
StringMapWrapper.set(this._optionals, controlName, false);
|
||||
this.updateValueAndValidity();
|
||||
}
|
||||
|
||||
/**
|
||||
* Check whether there is a control with the given name in the group.
|
||||
*/
|
||||
contains(controlName: string): boolean {
|
||||
var c = StringMapWrapper.contains(this.controls, controlName);
|
||||
return c && this._included(controlName);
|
||||
const c = StringMapWrapper.contains(this.controls, controlName);
|
||||
return c && this.get(controlName).enabled;
|
||||
}
|
||||
|
||||
setValue(value: {[key: string]: any}, {onlySelf}: {onlySelf?: boolean} = {}): void {
|
||||
@@ -562,6 +626,14 @@ export class FormGroup extends AbstractControl {
|
||||
this._updateTouched({onlySelf: onlySelf});
|
||||
}
|
||||
|
||||
getRawValue(): Object {
|
||||
return this._reduceChildren(
|
||||
{}, (acc: {[k: string]: AbstractControl}, control: AbstractControl, name: string) => {
|
||||
acc[name] = control.value;
|
||||
return acc;
|
||||
});
|
||||
}
|
||||
|
||||
/** @internal */
|
||||
_throwIfControlMissing(name: string): void {
|
||||
if (!Object.keys(this.controls).length) {
|
||||
@@ -601,7 +673,9 @@ export class FormGroup extends AbstractControl {
|
||||
_reduceValue() {
|
||||
return this._reduceChildren(
|
||||
{}, (acc: {[k: string]: AbstractControl}, control: AbstractControl, name: string) => {
|
||||
acc[name] = control.value;
|
||||
if (control.enabled || this.disabled) {
|
||||
acc[name] = control.value;
|
||||
}
|
||||
return acc;
|
||||
});
|
||||
}
|
||||
@@ -609,18 +683,19 @@ export class FormGroup extends AbstractControl {
|
||||
/** @internal */
|
||||
_reduceChildren(initValue: any, fn: Function) {
|
||||
var res = initValue;
|
||||
this._forEachChild((control: AbstractControl, name: string) => {
|
||||
if (this._included(name)) {
|
||||
res = fn(res, control, name);
|
||||
}
|
||||
});
|
||||
this._forEachChild(
|
||||
(control: AbstractControl, name: string) => { res = fn(res, control, name); });
|
||||
return res;
|
||||
}
|
||||
|
||||
/** @internal */
|
||||
_included(controlName: string): boolean {
|
||||
var isOptional = StringMapWrapper.contains(this._optionals, controlName);
|
||||
return !isOptional || StringMapWrapper.get(this._optionals, controlName);
|
||||
_allControlsDisabled(): boolean {
|
||||
for (let controlName of Object.keys(this.controls)) {
|
||||
if (this.controls[controlName].enabled) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return !StringMapWrapper.isEmpty(this.controls);
|
||||
}
|
||||
|
||||
/** @internal */
|
||||
@@ -729,6 +804,8 @@ export class FormArray extends AbstractControl {
|
||||
this._updateTouched({onlySelf: onlySelf});
|
||||
}
|
||||
|
||||
getRawValue(): any[] { return this.controls.map((control) => control.value); }
|
||||
|
||||
/** @internal */
|
||||
_throwIfControlMissing(index: number): void {
|
||||
if (!this.controls.length) {
|
||||
@@ -748,11 +825,14 @@ export class FormArray extends AbstractControl {
|
||||
}
|
||||
|
||||
/** @internal */
|
||||
_updateValue(): void { this._value = this.controls.map((control) => control.value); }
|
||||
_updateValue(): void {
|
||||
this._value = this.controls.filter((control) => control.enabled || this.disabled)
|
||||
.map((control) => control.value);
|
||||
}
|
||||
|
||||
/** @internal */
|
||||
_anyControls(condition: Function): boolean {
|
||||
return this.controls.some((control: AbstractControl) => condition(control));
|
||||
return this.controls.some((control: AbstractControl) => control.enabled && condition(control));
|
||||
}
|
||||
|
||||
/** @internal */
|
||||
@@ -768,4 +848,12 @@ export class FormArray extends AbstractControl {
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/** @internal */
|
||||
_allControlsDisabled(): boolean {
|
||||
for (let control of this.controls) {
|
||||
if (control.enabled) return false;
|
||||
}
|
||||
return !!this.controls.length;
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user