feat(forms): add support for disabled controls (#10994)

This commit is contained in:
Kara
2016-08-24 16:58:43 -07:00
committed by Victor Berchet
parent 4f8f8cfc66
commit 2b313e4979
24 changed files with 1335 additions and 343 deletions
@@ -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;
+9 -9
View File
@@ -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);
}
/**
+132 -44
View File
@@ -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;
}
}