Skip to content

Commit bfc9ff5

Browse files
committed
fix(checkbox, input, radio, slide-toggle): implement setDisabledState from ControlValueAccessor
Implements the `setDisabledState` method from the `ControlValueAccessor` interface in all of the input-related components, in order to support disabling via reactive forms. Note that the `select` component is missing the implementation, however there's a pending PR for it already (#1667). Fixes #1171.
1 parent a0d85d8 commit bfc9ff5

File tree

8 files changed

+207
-30
lines changed

8 files changed

+207
-30
lines changed

src/lib/checkbox/checkbox.spec.ts

Lines changed: 49 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,8 @@ import {
88
import {
99
NgControl,
1010
FormsModule,
11+
ReactiveFormsModule,
12+
FormControl,
1113
} from '@angular/forms';
1214
import {Component, DebugElement} from '@angular/core';
1315
import {By} from '@angular/platform-browser';
@@ -21,7 +23,7 @@ describe('MdCheckbox', () => {
2123

2224
beforeEach(async(() => {
2325
TestBed.configureTestingModule({
24-
imports: [MdCheckboxModule.forRoot(), FormsModule],
26+
imports: [MdCheckboxModule.forRoot(), FormsModule, ReactiveFormsModule],
2527
declarations: [
2628
SingleCheckbox,
2729
CheckboxWithFormDirectives,
@@ -31,6 +33,7 @@ describe('MdCheckbox', () => {
3133
CheckboxWithAriaLabelledby,
3234
CheckboxWithNameAttribute,
3335
CheckboxWithChangeEvent,
36+
CheckboxWithFormControl,
3437
],
3538
});
3639

@@ -550,18 +553,48 @@ describe('MdCheckbox', () => {
550553
expect(inputElement.getAttribute('name')).toBe('test-name');
551554
});
552555
});
556+
557+
558+
describe('with form control', () => {
559+
let checkboxDebugElement: DebugElement;
560+
let checkboxInstance: MdCheckbox;
561+
let testComponent: CheckboxWithFormControl;
562+
563+
beforeEach(() => {
564+
fixture = TestBed.createComponent(CheckboxWithFormControl);
565+
fixture.detectChanges();
566+
567+
checkboxDebugElement = fixture.debugElement.query(By.directive(MdCheckbox));
568+
checkboxInstance = checkboxDebugElement.componentInstance;
569+
testComponent = fixture.debugElement.componentInstance;
570+
});
571+
572+
it('should toggle the disabled state', () => {
573+
expect(checkboxInstance.disabled).toBe(false);
574+
575+
testComponent.formControl.disable();
576+
fixture.detectChanges();
577+
578+
expect(checkboxInstance.disabled).toBe(true);
579+
580+
testComponent.formControl.enable();
581+
fixture.detectChanges();
582+
583+
expect(checkboxInstance.disabled).toBe(false);
584+
});
585+
});
553586
});
554587

555588
/** Simple component for testing a single checkbox. */
556589
@Component({
557590
template: `
558-
<div (click)="parentElementClicked = true" (keyup)="parentElementKeyedUp = true">
559-
<md-checkbox
591+
<div (click)="parentElementClicked = true" (keyup)="parentElementKeyedUp = true">
592+
<md-checkbox
560593
id="simple-check"
561594
[required]="isRequired"
562595
[align]="alignment"
563-
[checked]="isChecked"
564-
[indeterminate]="isIndeterminate"
596+
[checked]="isChecked"
597+
[indeterminate]="isIndeterminate"
565598
[disabled]="isDisabled"
566599
[color]="checkboxColor"
567600
(change)="changeCount = changeCount + 1"
@@ -612,9 +645,9 @@ class MultipleCheckboxes { }
612645
/** Simple test component with tabIndex */
613646
@Component({
614647
template: `
615-
<md-checkbox
616-
[tabindex]="customTabIndex"
617-
[disabled]="isDisabled"
648+
<md-checkbox
649+
[tabindex]="customTabIndex"
650+
[disabled]="isDisabled"
618651
[disableRipple]="disableRipple">
619652
</md-checkbox>`,
620653
})
@@ -649,3 +682,11 @@ class CheckboxWithNameAttribute {}
649682
class CheckboxWithChangeEvent {
650683
lastEvent: MdCheckboxChange;
651684
}
685+
686+
/** Test component with reactive forms */
687+
@Component({
688+
template: `<md-checkbox [formControl]="formControl"></md-checkbox>`
689+
})
690+
class CheckboxWithFormControl {
691+
formControl = new FormControl();
692+
}

src/lib/checkbox/checkbox.ts

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -112,7 +112,7 @@ export class MdCheckbox implements ControlValueAccessor {
112112
/** Whether or not the checkbox should come before or after the label. */
113113
@Input() align: 'start' | 'end' = 'start';
114114

115-
private _disabled: boolean;
115+
private _disabled: boolean = false;
116116

117117
/**
118118
* Whether the checkbox is disabled. When the checkbox is disabled it cannot be interacted with.
@@ -241,6 +241,13 @@ export class MdCheckbox implements ControlValueAccessor {
241241
this.onTouched = fn;
242242
}
243243

244+
/**
245+
* Implemented as a part of ControlValueAccessor.
246+
*/
247+
setDisabledState(isDisabled: boolean) {
248+
this.disabled = isDisabled;
249+
}
250+
244251
private _transitionCheckState(newState: TransitionCheckState) {
245252
let oldState = this._currentCheckState;
246253
let renderer = this._renderer;

src/lib/input/input.spec.ts

Lines changed: 29 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@ import {
33
TestBed,
44
} from '@angular/core/testing';
55
import {Component} from '@angular/core';
6-
import {FormsModule} from '@angular/forms';
6+
import {FormsModule, ReactiveFormsModule, FormControl} from '@angular/forms';
77
import {By} from '@angular/platform-browser';
88
import {MdInput, MdInputModule} from './input';
99

@@ -14,7 +14,7 @@ function isInternetExplorer11() {
1414
describe('MdInput', function () {
1515
beforeEach(async(() => {
1616
TestBed.configureTestingModule({
17-
imports: [MdInputModule.forRoot(), FormsModule],
17+
imports: [MdInputModule.forRoot(), FormsModule, ReactiveFormsModule],
1818
declarations: [
1919
MdInputNumberTypeConservedTestComponent,
2020
MdInputPlaceholderRequiredTestComponent,
@@ -58,6 +58,7 @@ describe('MdInput', function () {
5858
MdInputPasswordTestController,
5959
MdInputNumberTestController,
6060
MdTextareaWithBindings,
61+
MdInputWithFormControl,
6162
],
6263
});
6364

@@ -621,6 +622,27 @@ describe('MdInput', function () {
621622
expect(inputElement.name).toBe('some-name');
622623
});
623624

625+
it('toggles the disabled state when used with a FormControl', () => {
626+
let fixture = TestBed.createComponent(MdInputWithFormControl);
627+
628+
fixture.detectChanges();
629+
630+
let input: MdInput = fixture.debugElement.query(By.directive(MdInput)).componentInstance;
631+
let testComponent: MdInputWithFormControl = fixture.debugElement.componentInstance;
632+
633+
expect(input.disabled).toBe(false);
634+
635+
testComponent.formControl.disable();
636+
fixture.detectChanges();
637+
638+
expect(input.disabled).toBe(true);
639+
640+
testComponent.formControl.enable();
641+
fixture.detectChanges();
642+
643+
expect(input.disabled).toBe(false);
644+
});
645+
624646
describe('md-textarea', () => {
625647
it('supports the rows, cols, and wrap attributes', () => {
626648
let fixture = TestBed.createComponent(MdTextareaWithBindings);
@@ -807,6 +829,11 @@ class MdInputNumberTestController {
807829
placeholder: string = '';
808830
}
809831

832+
@Component({template: `<md-input [formControl]="formControl"></md-input>`})
833+
class MdInputWithFormControl {
834+
formControl = new FormControl();
835+
}
836+
810837
@Component({template:
811838
`<md-textarea [rows]="rows" [cols]="cols" [wrap]="wrap" placeholder="Snacks"></md-textarea>`})
812839
class MdTextareaWithBindings {

src/lib/input/input.ts

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -292,6 +292,13 @@ export class MdInput implements ControlValueAccessor, AfterContentInit, OnChange
292292
this._onTouchedCallback = fn;
293293
}
294294

295+
/**
296+
* Implemented as a part of ControlValueAccessor.
297+
*/
298+
setDisabledState(isDisabled: boolean) {
299+
this.disabled = isDisabled;
300+
}
301+
295302
/** TODO: internal */
296303
ngAfterContentInit() {
297304
this._validateConstraints();

src/lib/radio/radio.spec.ts

Lines changed: 48 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
import {async, ComponentFixture, TestBed, fakeAsync, tick} from '@angular/core/testing';
2-
import {NgControl, FormsModule} from '@angular/forms';
2+
import {NgControl, FormsModule, ReactiveFormsModule, FormControl} from '@angular/forms';
33
import {Component, DebugElement} from '@angular/core';
44
import {By} from '@angular/platform-browser';
55
import {MdRadioGroup, MdRadioButton, MdRadioChange, MdRadioModule} from './radio';
@@ -9,10 +9,11 @@ describe('MdRadio', () => {
99

1010
beforeEach(async(() => {
1111
TestBed.configureTestingModule({
12-
imports: [MdRadioModule.forRoot(), FormsModule],
12+
imports: [MdRadioModule.forRoot(), FormsModule, ReactiveFormsModule],
1313
declarations: [
1414
RadiosInsideRadioGroup,
1515
RadioGroupWithNgModel,
16+
RadioGroupWithFormControl,
1617
StandaloneRadioButtons,
1718
],
1819
});
@@ -152,7 +153,7 @@ describe('MdRadio', () => {
152153
expect(spies[1]).toHaveBeenCalledTimes(1);
153154
});
154155

155-
it(`should not emit a change event from the radio group when change group value
156+
it(`should not emit a change event from the radio group when change group value
156157
programmatically`, () => {
157158
expect(groupInstance.value).toBeFalsy();
158159

@@ -235,7 +236,7 @@ describe('MdRadio', () => {
235236
}
236237
}));
237238

238-
it(`should update the group's selected radio to null when unchecking that radio
239+
it(`should update the group's selected radio to null when unchecking that radio
239240
programmatically`, () => {
240241
let changeSpy = jasmine.createSpy('radio-group change listener');
241242
groupInstance.change.subscribe(changeSpy);
@@ -386,6 +387,36 @@ describe('MdRadio', () => {
386387
});
387388
});
388389

390+
describe('group with FormControl', () => {
391+
let fixture: ComponentFixture<RadioGroupWithFormControl>;
392+
let groupDebugElement: DebugElement;
393+
let groupInstance: MdRadioGroup;
394+
let testComponent: RadioGroupWithFormControl;
395+
396+
beforeEach(() => {
397+
fixture = TestBed.createComponent(RadioGroupWithFormControl);
398+
fixture.detectChanges();
399+
400+
testComponent = fixture.debugElement.componentInstance;
401+
groupDebugElement = fixture.debugElement.query(By.directive(MdRadioGroup));
402+
groupInstance = groupDebugElement.injector.get(MdRadioGroup);
403+
});
404+
405+
it('should toggle the disabled state', () => {
406+
expect(groupInstance.disabled).toBeFalsy();
407+
408+
testComponent.formControl.disable();
409+
fixture.detectChanges();
410+
411+
expect(groupInstance.disabled).toBeTruthy();
412+
413+
testComponent.formControl.enable();
414+
fixture.detectChanges();
415+
416+
expect(groupInstance.disabled).toBeFalsy();
417+
});
418+
});
419+
389420
describe('as standalone', () => {
390421
let fixture: ComponentFixture<StandaloneRadioButtons>;
391422
let radioDebugElements: DebugElement[];
@@ -514,11 +545,11 @@ class RadiosInsideRadioGroup {
514545
<md-radio-button name="season" value="spring">Spring</md-radio-button>
515546
<md-radio-button name="season" value="summer">Summer</md-radio-button>
516547
<md-radio-button name="season" value="autum">Autumn</md-radio-button>
517-
548+
518549
<md-radio-button name="weather" value="warm">Spring</md-radio-button>
519550
<md-radio-button name="weather" value="hot">Summer</md-radio-button>
520551
<md-radio-button name="weather" value="cool">Autumn</md-radio-button>
521-
552+
522553
<span id="xyz">Baby Banana</span>
523554
<md-radio-button name="fruit" value="banana" aria-label="Banana" aria-labelledby="xyz">
524555
</md-radio-button>
@@ -547,6 +578,17 @@ class RadioGroupWithNgModel {
547578
lastEvent: MdRadioChange;
548579
}
549580

581+
@Component({
582+
template: `
583+
<md-radio-group [formControl]="formControl">
584+
<md-radio-button value="1">One</md-radio-button>
585+
</md-radio-group>
586+
`
587+
})
588+
class RadioGroupWithFormControl {
589+
formControl = new FormControl();
590+
}
591+
550592
// TODO(jelbourn): remove everything below when Angular supports faking events.
551593

552594
/**

src/lib/radio/radio.ts

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -224,6 +224,13 @@ export class MdRadioGroup implements AfterContentInit, ControlValueAccessor {
224224
registerOnTouched(fn: any) {
225225
this.onTouched = fn;
226226
}
227+
228+
/**
229+
* Implemented as a part of ControlValueAccessor.
230+
*/
231+
setDisabledState(isDisabled: boolean) {
232+
this.disabled = isDisabled;
233+
}
227234
}
228235

229236

0 commit comments

Comments
 (0)