Skip to content

Commit 2b313e4

Browse files
karavicb
authored andcommitted
feat(forms): add support for disabled controls (#10994)
1 parent 4f8f8cf commit 2b313e4

24 files changed

+1340
-348
lines changed

modules/@angular/forms/src/directives/abstract_control_directive.ts

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,10 @@ export abstract class AbstractControlDirective {
4141

4242
get untouched(): boolean { return isPresent(this.control) ? this.control.untouched : null; }
4343

44+
get disabled(): boolean { return isPresent(this.control) ? this.control.disabled : null; }
45+
46+
get enabled(): boolean { return isPresent(this.control) ? this.control.enabled : null; }
47+
4448
get statusChanges(): Observable<any> {
4549
return isPresent(this.control) ? this.control.statusChanges : null;
4650
}

modules/@angular/forms/src/directives/checkbox_value_accessor.ts

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -43,4 +43,8 @@ export class CheckboxControlValueAccessor implements ControlValueAccessor {
4343
}
4444
registerOnChange(fn: (_: any) => {}): void { this.onChange = fn; }
4545
registerOnTouched(fn: () => {}): void { this.onTouched = fn; }
46+
47+
setDisabledState(isDisabled: boolean): void {
48+
this._renderer.setElementProperty(this._elementRef.nativeElement, 'disabled', isDisabled);
49+
}
4650
}

modules/@angular/forms/src/directives/control_value_accessor.ts

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,14 @@ export interface ControlValueAccessor {
3333
* Set the function to be called when the control receives a touch event.
3434
*/
3535
registerOnTouched(fn: any): void;
36+
37+
/**
38+
* This function is called when the control status changes to or from "DISABLED".
39+
* Depending on the value, it will enable or disable the appropriate DOM element.
40+
*
41+
* @param isDisabled
42+
*/
43+
setDisabledState?(isDisabled: boolean): void;
3644
}
3745

3846
/**

modules/@angular/forms/src/directives/default_value_accessor.ts

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -51,4 +51,8 @@ export class DefaultValueAccessor implements ControlValueAccessor {
5151

5252
registerOnChange(fn: (_: any) => void): void { this.onChange = fn; }
5353
registerOnTouched(fn: () => void): void { this.onTouched = fn; }
54+
55+
setDisabledState(isDisabled: boolean): void {
56+
this._renderer.setElementProperty(this._elementRef.nativeElement, 'disabled', isDisabled);
57+
}
5458
}

modules/@angular/forms/src/directives/ng_form.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -104,8 +104,8 @@ export class NgForm extends ControlContainer implements Form {
104104
@Optional() @Self() @Inject(NG_VALIDATORS) validators: any[],
105105
@Optional() @Self() @Inject(NG_ASYNC_VALIDATORS) asyncValidators: any[]) {
106106
super();
107-
this.form = new FormGroup(
108-
{}, null, composeValidators(validators), composeAsyncValidators(asyncValidators));
107+
this.form =
108+
new FormGroup({}, composeValidators(validators), composeAsyncValidators(asyncValidators));
109109
}
110110

111111
get submitted(): boolean { return this._submitted; }

modules/@angular/forms/src/directives/ng_model.ts

Lines changed: 19 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -64,9 +64,11 @@ export class NgModel extends NgControl implements OnChanges,
6464
_registered = false;
6565
viewModel: any;
6666

67-
@Input('ngModel') model: any;
6867
@Input() name: string;
68+
@Input() disabled: boolean;
69+
@Input('ngModel') model: any;
6970
@Input('ngModelOptions') options: {name?: string, standalone?: boolean};
71+
7072
@Output('ngModelChange') update = new EventEmitter();
7173

7274
constructor(@Optional() @Host() private _parent: ControlContainer,
@@ -81,6 +83,9 @@ export class NgModel extends NgControl implements OnChanges,
8183
ngOnChanges(changes: SimpleChanges) {
8284
this._checkForErrors();
8385
if (!this._registered) this._setUpControl();
86+
if ('disabled' in changes) {
87+
this._updateDisabled(changes);
88+
}
8489

8590
if (isPropertyUpdated(changes, this.viewModel)) {
8691
this._updateValue(this.model);
@@ -153,4 +158,17 @@ export class NgModel extends NgControl implements OnChanges,
153158
resolvedPromise.then(
154159
() => { this.control.setValue(value, {emitViewToModelChange: false}); });
155160
}
161+
162+
private _updateDisabled(changes: SimpleChanges) {
163+
const disabledValue = changes['disabled'].currentValue;
164+
const isDisabled = disabledValue != null && disabledValue != false;
165+
166+
resolvedPromise.then(() => {
167+
if (isDisabled && !this.control.disabled) {
168+
this.control.disable();
169+
} else if (!isDisabled && this.control.disabled) {
170+
this.control.enable();
171+
}
172+
});
173+
}
156174
}

modules/@angular/forms/src/directives/number_value_accessor.ts

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -53,4 +53,8 @@ export class NumberValueAccessor implements ControlValueAccessor {
5353
this.onChange = (value) => { fn(value == '' ? null : NumberWrapper.parseFloat(value)); };
5454
}
5555
registerOnTouched(fn: () => void): void { this.onTouched = fn; }
56+
57+
setDisabledState(isDisabled: boolean): void {
58+
this._renderer.setElementProperty(this._elementRef.nativeElement, 'disabled', isDisabled);
59+
}
5660
}

modules/@angular/forms/src/directives/radio_control_value_accessor.ts

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -127,6 +127,10 @@ export class RadioControlValueAccessor implements ControlValueAccessor,
127127

128128
registerOnTouched(fn: () => {}): void { this.onTouched = fn; }
129129

130+
setDisabledState(isDisabled: boolean): void {
131+
this._renderer.setElementProperty(this._elementRef.nativeElement, 'disabled', isDisabled);
132+
}
133+
130134
private _checkName(): void {
131135
if (this.name && this.formControlName && this.name !== this.formControlName) {
132136
this._throwNameError();

modules/@angular/forms/src/directives/reactive_directives/form_control_directive.ts

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,9 +12,9 @@ import {EventEmitter} from '../../facade/async';
1212
import {StringMapWrapper} from '../../facade/collection';
1313
import {FormControl} from '../../model';
1414
import {NG_ASYNC_VALIDATORS, NG_VALIDATORS} from '../../validators';
15-
1615
import {ControlValueAccessor, NG_VALUE_ACCESSOR} from '../control_value_accessor';
1716
import {NgControl} from '../ng_control';
17+
import {ReactiveErrors} from '../reactive_errors';
1818
import {composeAsyncValidators, composeValidators, isPropertyUpdated, selectValueAccessor, setUpControl} from '../shared';
1919
import {AsyncValidatorFn, ValidatorFn} from '../validators';
2020

@@ -81,6 +81,9 @@ export class FormControlDirective extends NgControl implements OnChanges {
8181
@Input('ngModel') model: any;
8282
@Output('ngModelChange') update = new EventEmitter();
8383

84+
@Input('disabled')
85+
set disabled(isDisabled: boolean) { ReactiveErrors.disabledAttrWarning(); }
86+
8487
constructor(@Optional() @Self() @Inject(NG_VALIDATORS) private _validators:
8588
/* Array<Validator|Function> */ any[],
8689
@Optional() @Self() @Inject(NG_ASYNC_VALIDATORS) private _asyncValidators:
@@ -94,6 +97,7 @@ export class FormControlDirective extends NgControl implements OnChanges {
9497
ngOnChanges(changes: SimpleChanges): void {
9598
if (this._isControlChanged(changes)) {
9699
setUpControl(this.form, this);
100+
if (this.control.disabled) this.valueAccessor.setDisabledState(true);
97101
this.form.updateValueAndValidity({emitEvent: false});
98102
}
99103
if (isPropertyUpdated(changes, this.viewModel)) {

modules/@angular/forms/src/directives/reactive_directives/form_control_name.ts

Lines changed: 54 additions & 53 deletions
Original file line numberDiff line numberDiff line change
@@ -106,57 +106,58 @@ export class FormControlName extends NgControl implements OnChanges, OnDestroy {
106106
@Input('ngModel') model: any;
107107
@Output('ngModelChange') update = new EventEmitter();
108108

109-
constructor(@Optional() @Host() @SkipSelf() private _parent: ControlContainer,
110-
@Optional() @Self() @Inject(NG_VALIDATORS) private _validators:
111-
/* Array<Validator|Function> */ any[],
112-
@Optional() @Self() @Inject(NG_ASYNC_VALIDATORS) private _asyncValidators:
113-
/* Array<Validator|Function> */ any[],
114-
@Optional() @Self() @Inject(NG_VALUE_ACCESSOR)
115-
valueAccessors: ControlValueAccessor[]) {
116-
super();
117-
this.valueAccessor = selectValueAccessor(this, valueAccessors);
118-
}
119-
120-
ngOnChanges(changes: SimpleChanges) {
121-
if (!this._added) {
122-
this._checkParentType();
123-
this.formDirective.addControl(this);
124-
this._added = true;
125-
}
126-
if (isPropertyUpdated(changes, this.viewModel)) {
127-
this.viewModel = this.model;
128-
this.formDirective.updateModel(this, this.model);
129-
}
130-
}
131-
132-
ngOnDestroy(): void { this.formDirective.removeControl(this); }
133-
134-
viewToModelUpdate(newValue: any): void {
135-
this.viewModel = newValue;
136-
this.update.emit(newValue);
137-
}
138-
139-
get path(): string[] { return controlPath(this.name, this._parent); }
140-
141-
get formDirective(): any { return this._parent.formDirective; }
142-
143-
get validator(): ValidatorFn { return composeValidators(this._validators); }
144-
145-
get asyncValidator(): AsyncValidatorFn {
146-
return composeAsyncValidators(this._asyncValidators);
147-
}
148-
149-
get control(): FormControl { return this.formDirective.getControl(this); }
150-
151-
private _checkParentType(): void {
152-
if (!(this._parent instanceof FormGroupName) &&
153-
this._parent instanceof AbstractFormGroupDirective) {
154-
ReactiveErrors.ngModelGroupException();
155-
} else if (
156-
!(this._parent instanceof FormGroupName) &&
157-
!(this._parent instanceof FormGroupDirective) &&
158-
!(this._parent instanceof FormArrayName)) {
159-
ReactiveErrors.controlParentException();
160-
}
161-
}
109+
@Input('disabled')
110+
set disabled(isDisabled: boolean) { ReactiveErrors.disabledAttrWarning(); }
111+
112+
constructor(
113+
@Optional() @Host() @SkipSelf() private _parent: ControlContainer,
114+
@Optional() @Self() @Inject(NG_VALIDATORS) private _validators:
115+
/* Array<Validator|Function> */ any[],
116+
@Optional() @Self() @Inject(NG_ASYNC_VALIDATORS) private _asyncValidators:
117+
/* Array<Validator|Function> */ any[],
118+
@Optional() @Self() @Inject(NG_VALUE_ACCESSOR) valueAccessors: ControlValueAccessor[]) {
119+
super();
120+
this.valueAccessor = selectValueAccessor(this, valueAccessors);
121+
}
122+
123+
ngOnChanges(changes: SimpleChanges) {
124+
if (!this._added) {
125+
this._checkParentType();
126+
this.formDirective.addControl(this);
127+
if (this.control.disabled) this.valueAccessor.setDisabledState(true);
128+
this._added = true;
129+
}
130+
if (isPropertyUpdated(changes, this.viewModel)) {
131+
this.viewModel = this.model;
132+
this.formDirective.updateModel(this, this.model);
133+
}
134+
}
135+
136+
ngOnDestroy(): void { this.formDirective.removeControl(this); }
137+
138+
viewToModelUpdate(newValue: any): void {
139+
this.viewModel = newValue;
140+
this.update.emit(newValue);
141+
}
142+
143+
get path(): string[] { return controlPath(this.name, this._parent); }
144+
145+
get formDirective(): any { return this._parent.formDirective; }
146+
147+
get validator(): ValidatorFn { return composeValidators(this._validators); }
148+
149+
get asyncValidator(): AsyncValidatorFn { return composeAsyncValidators(this._asyncValidators); }
150+
151+
get control(): FormControl { return this.formDirective.getControl(this); }
152+
153+
private _checkParentType(): void {
154+
if (!(this._parent instanceof FormGroupName) &&
155+
this._parent instanceof AbstractFormGroupDirective) {
156+
ReactiveErrors.ngModelGroupException();
157+
} else if (
158+
!(this._parent instanceof FormGroupName) && !(this._parent instanceof FormGroupDirective) &&
159+
!(this._parent instanceof FormArrayName)) {
160+
ReactiveErrors.controlParentException();
161+
}
162+
}
162163
}

0 commit comments

Comments
 (0)