Skip to content

Commit 77a960c

Browse files
crisbetotinayuangao
authored andcommitted
fix(checkbox, input, radio, slide-toggle): implement setDisabledState from ControlValueAccessor (#1750)
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 f1b9b2c commit 77a960c

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

@@ -561,18 +564,48 @@ describe('MdCheckbox', () => {
561564
expect(inputElement.getAttribute('name')).toBe('test-name');
562565
});
563566
});
567+
568+
569+
describe('with form control', () => {
570+
let checkboxDebugElement: DebugElement;
571+
let checkboxInstance: MdCheckbox;
572+
let testComponent: CheckboxWithFormControl;
573+
574+
beforeEach(() => {
575+
fixture = TestBed.createComponent(CheckboxWithFormControl);
576+
fixture.detectChanges();
577+
578+
checkboxDebugElement = fixture.debugElement.query(By.directive(MdCheckbox));
579+
checkboxInstance = checkboxDebugElement.componentInstance;
580+
testComponent = fixture.debugElement.componentInstance;
581+
});
582+
583+
it('should toggle the disabled state', () => {
584+
expect(checkboxInstance.disabled).toBe(false);
585+
586+
testComponent.formControl.disable();
587+
fixture.detectChanges();
588+
589+
expect(checkboxInstance.disabled).toBe(true);
590+
591+
testComponent.formControl.enable();
592+
fixture.detectChanges();
593+
594+
expect(checkboxInstance.disabled).toBe(false);
595+
});
596+
});
564597
});
565598

566599
/** Simple component for testing a single checkbox. */
567600
@Component({
568601
template: `
569-
<div (click)="parentElementClicked = true" (keyup)="parentElementKeyedUp = true">
570-
<md-checkbox
602+
<div (click)="parentElementClicked = true" (keyup)="parentElementKeyedUp = true">
603+
<md-checkbox
571604
id="simple-check"
572605
[required]="isRequired"
573606
[align]="alignment"
574-
[checked]="isChecked"
575-
[indeterminate]="isIndeterminate"
607+
[checked]="isChecked"
608+
[indeterminate]="isIndeterminate"
576609
[disabled]="isDisabled"
577610
[color]="checkboxColor"
578611
(change)="changeCount = changeCount + 1"
@@ -623,9 +656,9 @@ class MultipleCheckboxes { }
623656
/** Simple test component with tabIndex */
624657
@Component({
625658
template: `
626-
<md-checkbox
627-
[tabindex]="customTabIndex"
628-
[disabled]="isDisabled"
659+
<md-checkbox
660+
[tabindex]="customTabIndex"
661+
[disabled]="isDisabled"
629662
[disableRipple]="disableRipple">
630663
</md-checkbox>`,
631664
})
@@ -660,3 +693,11 @@ class CheckboxWithNameAttribute {}
660693
class CheckboxWithChangeEvent {
661694
lastEvent: MdCheckboxChange;
662695
}
696+
697+
/** Test component with reactive forms */
698+
@Component({
699+
template: `<md-checkbox [formControl]="formControl"></md-checkbox>`
700+
})
701+
class CheckboxWithFormControl {
702+
formControl = new FormControl();
703+
}

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.
@@ -245,6 +245,13 @@ export class MdCheckbox implements ControlValueAccessor {
245245
this.onTouched = fn;
246246
}
247247

248+
/**
249+
* Implemented as a part of ControlValueAccessor.
250+
*/
251+
setDisabledState(isDisabled: boolean) {
252+
this.disabled = isDisabled;
253+
}
254+
248255
private _transitionCheckState(newState: TransitionCheckState) {
249256
let oldState = this._currentCheckState;
250257
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
@@ -293,6 +293,13 @@ export class MdInput implements ControlValueAccessor, AfterContentInit, OnChange
293293
this._onTouchedCallback = fn;
294294
}
295295

296+
/**
297+
* Implemented as a part of ControlValueAccessor.
298+
*/
299+
setDisabledState(isDisabled: boolean) {
300+
this.disabled = isDisabled;
301+
}
302+
296303
/** TODO: internal */
297304
ngAfterContentInit() {
298305
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

@@ -246,7 +247,7 @@ describe('MdRadio', () => {
246247
}
247248
}));
248249

249-
it(`should update the group's selected radio to null when unchecking that radio
250+
it(`should update the group's selected radio to null when unchecking that radio
250251
programmatically`, () => {
251252
let changeSpy = jasmine.createSpy('radio-group change listener');
252253
groupInstance.change.subscribe(changeSpy);
@@ -420,6 +421,36 @@ describe('MdRadio', () => {
420421
});
421422
});
422423

424+
describe('group with FormControl', () => {
425+
let fixture: ComponentFixture<RadioGroupWithFormControl>;
426+
let groupDebugElement: DebugElement;
427+
let groupInstance: MdRadioGroup;
428+
let testComponent: RadioGroupWithFormControl;
429+
430+
beforeEach(() => {
431+
fixture = TestBed.createComponent(RadioGroupWithFormControl);
432+
fixture.detectChanges();
433+
434+
testComponent = fixture.debugElement.componentInstance;
435+
groupDebugElement = fixture.debugElement.query(By.directive(MdRadioGroup));
436+
groupInstance = groupDebugElement.injector.get(MdRadioGroup);
437+
});
438+
439+
it('should toggle the disabled state', () => {
440+
expect(groupInstance.disabled).toBeFalsy();
441+
442+
testComponent.formControl.disable();
443+
fixture.detectChanges();
444+
445+
expect(groupInstance.disabled).toBeTruthy();
446+
447+
testComponent.formControl.enable();
448+
fixture.detectChanges();
449+
450+
expect(groupInstance.disabled).toBeFalsy();
451+
});
452+
});
453+
423454
describe('as standalone', () => {
424455
let fixture: ComponentFixture<StandaloneRadioButtons>;
425456
let radioDebugElements: DebugElement[];
@@ -548,11 +579,11 @@ class RadiosInsideRadioGroup {
548579
<md-radio-button name="season" value="spring">Spring</md-radio-button>
549580
<md-radio-button name="season" value="summer">Summer</md-radio-button>
550581
<md-radio-button name="season" value="autum">Autumn</md-radio-button>
551-
582+
552583
<md-radio-button name="weather" value="warm">Spring</md-radio-button>
553584
<md-radio-button name="weather" value="hot">Summer</md-radio-button>
554585
<md-radio-button name="weather" value="cool">Autumn</md-radio-button>
555-
586+
556587
<span id="xyz">Baby Banana</span>
557588
<md-radio-button name="fruit" value="banana" aria-label="Banana" aria-labelledby="xyz">
558589
</md-radio-button>
@@ -581,6 +612,17 @@ class RadioGroupWithNgModel {
581612
lastEvent: MdRadioChange;
582613
}
583614

615+
@Component({
616+
template: `
617+
<md-radio-group [formControl]="formControl">
618+
<md-radio-button value="1">One</md-radio-button>
619+
</md-radio-group>
620+
`
621+
})
622+
class RadioGroupWithFormControl {
623+
formControl = new FormControl();
624+
}
625+
584626
// TODO(jelbourn): remove everything below when Angular supports faking events.
585627

586628
/**

src/lib/radio/radio.ts

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -228,6 +228,13 @@ export class MdRadioGroup implements AfterContentInit, ControlValueAccessor {
228228
registerOnTouched(fn: any) {
229229
this.onTouched = fn;
230230
}
231+
232+
/**
233+
* Implemented as a part of ControlValueAccessor.
234+
*/
235+
setDisabledState(isDisabled: boolean) {
236+
this.disabled = isDisabled;
237+
}
231238
}
232239

233240

0 commit comments

Comments
 (0)