Skip to content
This repository has been archived by the owner on Sep 16, 2022. It is now read-only.

Commit

Permalink
feature(forms): Support disabled state in directives.
Browse files Browse the repository at this point in the history
More work towards #1037

PiperOrigin-RevId: 193984475
  • Loading branch information
alorenzen authored and matanlurey committed Apr 23, 2018
1 parent c81566c commit 2fa81e5
Show file tree
Hide file tree
Showing 18 changed files with 179 additions and 10 deletions.
4 changes: 4 additions & 0 deletions angular_forms/CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,9 @@
## 2.0.0-alpha+3

### New Features

* Add `ngDisabled` input to all Control directives.

### Breaking Changes

* `NgControlGroup` can no longer be injected directly. It can still be
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,10 @@ abstract class AbstractControlDirective<T extends AbstractControl> {

bool get valid => control?.valid;

bool get disabled => control?.disabled;

bool get enabled => control?.enabled;

Map<String, dynamic> get errors => control?.errors;

bool get pristine => control?.pristine;
Expand All @@ -23,4 +27,9 @@ abstract class AbstractControlDirective<T extends AbstractControl> {
bool get untouched => control?.untouched;

List<String> get path => null;

void toggleDisabled(bool isDisabled) {
if (isDisabled && !control.disabled) control.markAsDisabled();
if (!isDisabled && !control.enabled) control.markAsEnabled();
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -42,5 +42,7 @@ class CheckboxControlValueAccessor extends Object
}

@override
void onDisabledChanged(bool isDisabled) {}
void onDisabledChanged(bool isDisabled) {
_element.disabled = isDisabled;
}
}
6 changes: 5 additions & 1 deletion angular_forms/lib/src/directives/default_value_accessor.dart
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,8 @@ import 'dart:html';
import 'dart:js_util' as js_util;

import 'package:angular/angular.dart';
import 'package:angular_forms/src/directives/shared.dart'
show setElementDisabled;

import 'control_value_accessor.dart';

Expand Down Expand Up @@ -43,5 +45,7 @@ class DefaultValueAccessor extends Object
}

@override
void onDisabledChanged(bool isDisabled) {}
void onDisabledChanged(bool isDisabled) {
setElementDisabled(_element, isDisabled);
}
}
22 changes: 22 additions & 0 deletions angular_forms/lib/src/directives/ng_control_group.dart
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
import 'dart:async';

import 'package:angular/angular.dart';

import '../model.dart' show ControlGroup;
Expand Down Expand Up @@ -62,6 +64,9 @@ class NgControlGroup extends ControlContainer implements OnInit, OnDestroy {
final ValidatorFn validator;
final ControlContainer _parent;

bool _isDisabled = false;
bool _disabledChanged = false;

NgControlGroup(@SkipSelf() this._parent,
@Optional() @Self() @Inject(NG_VALIDATORS) List validators)
: validator = composeValidators(validators);
Expand All @@ -72,9 +77,26 @@ class NgControlGroup extends ControlContainer implements OnInit, OnDestroy {
super.name = value;
}

@Input('ngDisabled')
set disabled(bool isDisabled) {
_isDisabled = isDisabled;
if (control != null) {
_disabledChanged = false;
toggleDisabled(isDisabled);
} else {
_disabledChanged = true;
}
}

@override
void ngOnInit() {
formDirective.addControlGroup(this);
if (_disabledChanged) {
scheduleMicrotask(() {
_disabledChanged = false;
toggleDisabled(_isDisabled);
});
}
}

@override
Expand Down
16 changes: 16 additions & 0 deletions angular_forms/lib/src/directives/ng_control_name.dart
Original file line number Diff line number Diff line change
Expand Up @@ -74,6 +74,7 @@ import 'shared.dart' show controlPath;
class NgControlName extends NgControl implements AfterChanges, OnDestroy {
final ControlContainer _parent;
final _update = new StreamController.broadcast();

bool _modelChanged = false;
dynamic _model;
@Input('ngModel')
Expand All @@ -86,6 +87,9 @@ class NgControlName extends NgControl implements AfterChanges, OnDestroy {
dynamic viewModel;
var _added = false;

bool _isDisabled = false;
bool _disabledChanged = false;

NgControlName(
@SkipSelf()
this._parent,
Expand All @@ -105,6 +109,12 @@ class NgControlName extends NgControl implements AfterChanges, OnDestroy {
super.name = value;
}

@Input('ngDisabled')
set disabled(bool isDisabled) {
_isDisabled = isDisabled;
_disabledChanged = true;
}

@Output('ngModelChange')
Stream get update => _update.stream;

Expand All @@ -121,6 +131,12 @@ class NgControlName extends NgControl implements AfterChanges, OnDestroy {
formDirective.updateModel(this, _model);
}
}
if (_disabledChanged) {
scheduleMicrotask(() {
_disabledChanged = false;
toggleDisabled(_isDisabled);
});
}
}

@override
Expand Down
5 changes: 5 additions & 0 deletions angular_forms/lib/src/directives/ng_form.dart
Original file line number Diff line number Diff line change
Expand Up @@ -81,6 +81,11 @@ class NgForm extends AbstractForm {
form = new ControlGroup({}, composeValidators(validators));
}

@Input('ngDisabled')
set disabled(bool isDisabled) {
toggleDisabled(isDisabled);
}

Map<String, AbstractControl> get controls => form.controls;

@override
Expand Down
7 changes: 7 additions & 0 deletions angular_forms/lib/src/directives/ng_model.dart
Original file line number Diff line number Diff line change
Expand Up @@ -91,6 +91,13 @@ class NgModel extends NgControl
_init(valueAccessors);
}

@Input('ngDisabled')
set disabled(bool isDisabled) {
setState(() {
toggleDisabled(isDisabled);
});
}

// This function prevents constructor inlining for smaller code size since
// NgModel is constructed for majority of form components.
void _init(List<ControlValueAccessor> valueAccessors) {
Expand Down
4 changes: 3 additions & 1 deletion angular_forms/lib/src/directives/number_value_accessor.dart
Original file line number Diff line number Diff line change
Expand Up @@ -41,5 +41,7 @@ class NumberValueAccessor extends Object
}

@override
void onDisabledChanged(bool isDisabled) {}
void onDisabledChanged(bool isDisabled) {
_element.disabled = isDisabled;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,8 @@ import 'dart:html';
import 'dart:js_util' as js_util;

import 'package:angular/angular.dart';
import 'package:angular_forms/src/directives/shared.dart'
show setElementDisabled;

import 'control_value_accessor.dart'
show ChangeHandler, ControlValueAccessor, NG_VALUE_ACCESSOR, TouchHandler;
Expand Down Expand Up @@ -116,5 +118,7 @@ class RadioControlValueAccessor extends Object
}

@override
void onDisabledChanged(bool isDisabled) {}
void onDisabledChanged(bool isDisabled) {
setElementDisabled(_element, isDisabled);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -61,7 +61,9 @@ class SelectControlValueAccessor extends Object
}

@override
void onDisabledChanged(bool isDisabled) {}
void onDisabledChanged(bool isDisabled) {
_element.disabled = isDisabled;
}

String _registerOption() => (_idCounter++).toString();

Expand Down
8 changes: 8 additions & 0 deletions angular_forms/lib/src/directives/shared.dart
Original file line number Diff line number Diff line change
@@ -1,3 +1,6 @@
import 'dart:html';
import 'dart:js_util' as js_util;

import '../model.dart' show Control, ControlGroup;
import '../validators.dart' show Validators;
import 'abstract_control_directive.dart' show AbstractControlDirective;
Expand Down Expand Up @@ -35,6 +38,7 @@ void setUpControl(Control control, NgControl dir) {
// model -> view
control.registerOnChange(
(dynamic newValue) => dir.valueAccessor?.writeValue(newValue));
control.disabledChanges.listen(dir.valueAccessor?.onDisabledChanged);
// touched
dir.valueAccessor.registerOnTouched(() => control.markAsTouched());
}
Expand Down Expand Up @@ -86,3 +90,7 @@ ControlValueAccessor selectValueAccessor(
_throwError(null, 'No valid value accessor for');
return null;
}

void setElementDisabled(HtmlElement element, bool isDisabled) {
js_util.setProperty(element, 'disabled', isDisabled);
}
5 changes: 5 additions & 0 deletions angular_forms/lib/src/model.dart
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,7 @@ abstract class AbstractControl<T> {
T _value;
final _valueChanges = new StreamController<T>.broadcast();
final _statusChanges = new StreamController<String>.broadcast();
final _disabledChanges = new StreamController<bool>.broadcast();
String _status;
Map<String, dynamic> _errors;
bool _pristine = true;
Expand Down Expand Up @@ -77,6 +78,8 @@ abstract class AbstractControl<T> {

Stream<String> get statusChanges => _statusChanges.stream;

Stream<bool> get disabledChanges => _disabledChanges.stream;

bool get pending => _status == PENDING;

void markAsTouched() {
Expand Down Expand Up @@ -126,6 +129,7 @@ abstract class AbstractControl<T> {
if (emitEvent) _emitEvent();

_updateAncestors(updateParent: updateParent, emitEvent: emitEvent);
_disabledChanges.add(true);
}

/// Enables the control. This means the control will be included in
Expand All @@ -143,6 +147,7 @@ abstract class AbstractControl<T> {
(c) => c.markAsEnabled(updateParent: false, emitEvent: emitEvent));
updateValueAndValidity(onlySelf: true, emitEvent: emitEvent);
_updateAncestors(updateParent: updateParent, emitEvent: emitEvent);
_disabledChanges.add(false);
}

void _updateAncestors({bool updateParent, bool emitEvent}) {
Expand Down
9 changes: 9 additions & 0 deletions angular_forms/test/model_test.dart
Original file line number Diff line number Diff line change
Expand Up @@ -379,6 +379,15 @@ void main() {
expect(control.disabled, false);
});

test('should update nested children', () {
var childControl = new Control();
group.addControl('nested', new ControlGroup({'child': childControl}));
group.markAsDisabled();
expect(childControl.disabled, true);
group.markAsEnabled();
expect(childControl.disabled, false);
});

test('should handle empty ControlGroup', () {
var emptyGroup = new ControlGroup({});
expect(emptyGroup.disabled, false);
Expand Down
22 changes: 20 additions & 2 deletions angular_forms/test/ng_control_group_test.dart
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
import 'dart:html';

@TestOn('browser')
import 'package:test/test.dart';
import 'package:angular/angular.dart';
Expand Down Expand Up @@ -31,6 +33,17 @@ void main() {
expect(cmp.controlGroup.untouched, cmp.groupModel.untouched);
});
});

test('should disable child controls', () async {
await fixture.update((cmp) {
cmp.disabled = true;
});
expect(fixture.assertOnlyInstance.inputElement.disabled, true);
await fixture.update((cmp) {
cmp.disabled = false;
});
expect(fixture.assertOnlyInstance.inputElement.disabled, false);
});
});
}

Expand All @@ -42,8 +55,8 @@ void main() {
],
template: '''
<div [ngFormModel]="formModel">
<div [ngControlGroup]="'group'" #controlGroup="ngForm">
<input [ngControl]="'login'" />
<div [ngControlGroup]="'group'" #controlGroup="ngForm" [ngDisabled]="disabled">
<input [ngControl]="'login'" #input />
</div>
</div>
''',
Expand All @@ -52,6 +65,11 @@ class NgControlGroupTest {
@ViewChild('controlGroup')
NgControlGroup controlGroup;

@ViewChild('input')
InputElement inputElement;

bool disabled = false;

ControlGroup formModel = FormBuilder.controlGroup({
'group': FormBuilder.controlGroup({'login': new Control(null)})
});
Expand Down
22 changes: 21 additions & 1 deletion angular_forms/test/ng_control_name_test.dart
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
import 'dart:html';

@TestOn('browser')
import 'package:test/test.dart';
import 'package:angular/angular.dart';
Expand Down Expand Up @@ -31,6 +33,14 @@ void main() {
expect(cmp.controlName.untouched, cmp.controlModel.untouched);
});
});

test('should disabled element', () async {
expect(fixture.assertOnlyInstance.inputElement.disabled, false);
await fixture.update((cmp) => cmp.disabled = true);
expect(fixture.assertOnlyInstance.inputElement.disabled, true);
await fixture.update((cmp) => cmp.disabled = false);
expect(fixture.assertOnlyInstance.inputElement.disabled, false);
});
});
}

Expand All @@ -41,17 +51,27 @@ void main() {
],
template: '''
<div [ngFormModel]="formModel">
<input [ngControl]="'login'" [(ngModel)]="loginValue" #login="ngForm" required />
<input [ngControl]="'login'"
[(ngModel)]="loginValue"
#login="ngForm"
#input
required
[ngDisabled]="disabled" />
</div>
''',
)
class NgControlNameTest {
@ViewChild('login')
NgControlName controlName;

@ViewChild('input')
InputElement inputElement;

String loginValue;

ControlGroup formModel = new ControlGroup({'login': new Control('login')});

bool disabled = false;

Control get controlModel => formModel.controls['login'];
}
Loading

0 comments on commit 2fa81e5

Please sign in to comment.