Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(radio): support ngModel on md-radio-group #209

Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 9 additions & 0 deletions src/components/radio/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,15 @@ A dynamic example, populated from a `data` variable:
</md-radio-group>
```

A dynamic example for use inside a form showing support for `[(ngModel)]`:
```html
<md-radio-group [(ngModel)]="chosenOption">
<md-radio-button *ngFor="#o of options" [value]="o.value">
{{o.label}}
</md-radio-button>
</md-radio-group>
```

## `<md-radio-group>`
### Properties

Expand Down
76 changes: 75 additions & 1 deletion src/components/radio/radio.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -258,6 +258,68 @@ export function main() {
});
}).then(done);
});

it('should bind value to model without initial value', (done: () => void) => {
builder
.overrideTemplate(TestApp, `
<md-radio-group [(ngModel)]="choice">
<md-radio-button [value]="0"></md-radio-button>
<md-radio-button [value]="1"></md-radio-button>
</md-radio-group>`)
.createAsync(TestApp)
.then((fixture) => {
fakeAsync(function() {
let buttons = fixture.debugElement.queryAll(By.css('md-radio-button'));
let group = fixture.debugElement.query(By.css('md-radio-group'));

fixture.detectChanges();
expect(buttons[0].componentInstance.checked).toBe(false);
expect(buttons[1].componentInstance.checked).toBe(false);
expect(fixture.componentInstance.choice).toBe(undefined);

group.componentInstance.selected = buttons[0].componentInstance;
fixture.detectChanges();
expect(isSinglySelected(buttons[0], buttons)).toBe(true);
expect(fixture.componentInstance.choice).toBe(0);

group.componentInstance.selected = buttons[1].componentInstance;
fixture.detectChanges();
expect(isSinglySelected(buttons[1], buttons)).toBe(true);
expect(fixture.componentInstance.choice).toBe(1);
});
}).then(done);
});

it('should bind value to model with initial value', (done: () => void) => {
builder
.overrideTemplate(TestAppWithInitialValue, `
<md-radio-group [(ngModel)]="choice">
<md-radio-button [value]="0"></md-radio-button>
<md-radio-button [value]="1"></md-radio-button>
</md-radio-group>`)
.createAsync(TestAppWithInitialValue)
.then((fixture) => {
fakeAsync(function() {
let buttons = fixture.debugElement.queryAll(By.css('md-radio-button'));
let group = fixture.debugElement.query(By.css('md-radio-group'));

fixture.detectChanges();
expect(isSinglySelected(buttons[1], buttons)).toBe(true);
expect(fixture.componentInstance.choice).toBe(1);

group.componentInstance.selected = buttons[0].componentInstance;
fixture.detectChanges();
expect(isSinglySelected(buttons[0], buttons)).toBe(true);
expect(fixture.componentInstance.choice).toBe(0);

group.componentInstance.selected = buttons[1].componentInstance;
fixture.detectChanges();
expect(isSinglySelected(buttons[1], buttons)).toBe(true);
expect(fixture.componentInstance.choice).toBe(1);
});
}).then(done);
});

});
}

Expand Down Expand Up @@ -289,4 +351,16 @@ function createEvent(name: string): Event {
providers: [MdRadioDispatcher],
template: ''
})
class TestApp {}
class TestApp {
choice: number;
}

/** Test component. */
@Component({
directives: [MdRadioButton, MdRadioGroup],
providers: [MdRadioDispatcher],
template: ''
})
class TestAppWithInitialValue {
choice: number = 1;
}
48 changes: 47 additions & 1 deletion src/components/radio/radio.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,14 +10,31 @@ import {
OnInit,
Optional,
Output,
Provider,
QueryList,
ViewEncapsulation,
forwardRef
} from 'angular2/core';

import {
NG_VALUE_ACCESSOR,
ControlValueAccessor
} from 'angular2/src/common/forms/directives/control_value_accessor';
import {CONST_EXPR} from 'angular2/src/facade/lang';

import {MdRadioDispatcher} from './radio_dispatcher';
export {MdRadioDispatcher} from './radio_dispatcher';

/**
* Provider Expression that allows md-radio-group to register as a ControlValueAccessor. This
* allows it to support [(ngModel)] and ngControl.
*/
const MD_RADIO_GROUP_CONTROL_VALUE_ACCESSOR = CONST_EXPR(new Provider(
NG_VALUE_ACCESSOR, {
useExisting: forwardRef(() => MdRadioGroup),
multi: true
}));

// TODO(mtlin):
// Ink ripple is currently placeholder.
// Determine motion spec for button transitions.
Expand All @@ -36,11 +53,12 @@ export class MdRadioChange {

@Directive({
selector: 'md-radio-group',
providers: [MD_RADIO_GROUP_CONTROL_VALUE_ACCESSOR],
host: {
'role': 'radiogroup',
},
})
export class MdRadioGroup implements AfterContentInit {
export class MdRadioGroup implements AfterContentInit, ControlValueAccessor {
/** The value for the radio group. Should match currently selected button. */
private _value: any = null;

Expand All @@ -53,6 +71,11 @@ export class MdRadioGroup implements AfterContentInit {
/** The currently selected radio button. Should match value. */
private _selected: MdRadioButton = null;

/** Change event subscription set up by registerOnChange (ControlValueAccessor). */
private _changeSubscription: {unsubscribe: () => any} = null;
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Add comment like

/** Change event subscription set up by registerOnChange (ControlValueAccessor). */ 


onTouched: () => any = () => {};

/** Event emitted when the group value changes. */
@Output()
change: EventEmitter<MdRadioChange> = new EventEmitter();
Expand Down Expand Up @@ -155,6 +178,25 @@ export class MdRadioGroup implements AfterContentInit {

selected.checked = true;
}

/** Implemented as part of ControlValueAccessor. */
writeValue(value: any) {
this.value = value;
}

/** Implemented as part of ControlValueAccessor. */
registerOnChange(fn: any) {
if (this._changeSubscription) {
this._changeSubscription.unsubscribe();
}
this._changeSubscription = <{unsubscribe: () => any}>this.change.subscribe(
(changeEvent: MdRadioChange) => { fn(changeEvent.value); });
}

/** Implemented as part of ControlValueAccessor. */
registerOnTouched(fn: any) {
this.onTouched = fn;
}
}


Expand Down Expand Up @@ -210,6 +252,10 @@ export class MdRadioButton implements OnInit {
if (this.id == null) {
this.id = `md-radio-${_uniqueIdCounter++}`;
}

if (this.radioGroup && this._value == this.radioGroup.value) {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Looks like this added behavior also needs a test.

this._checked = true;
}
}

/*
Expand Down
12 changes: 12 additions & 0 deletions src/demo-app/radio/radio-demo.html
Original file line number Diff line number Diff line change
@@ -1,8 +1,10 @@
<h1>Basic Example</h1>
<section class="demo-section">
<md-radio-button name="group1">Option 1</md-radio-button>
<md-radio-button name="group1">Option 2</md-radio-button>
<md-radio-button name="group1" disabled="true">Option 3 (disabled)</md-radio-button>
</section>
<h1>Dynamic Example</h1>
<section class="demo-section">
<div>
<span>isDisabled: {{isDisabled}}</span>
Expand All @@ -16,3 +18,13 @@
<md-radio-button value="option_3">Option 3</md-radio-button>
</md-radio-group>
</section>
<h1>Favorite Season Example</h1>
<h2>Dynamic Example with two-way data-binding</h2>
<section class="demo-section">
<md-radio-group name="more_options" [(ngModel)]="favoriteSeason">
<md-radio-button *ngFor="#season of seasonOptions" name="more_options" [value]="season">
{{season}}
</md-radio-button>
</md-radio-group>
<p>Your favorite season is: {{favoriteSeason}}</p>
</section>
7 changes: 7 additions & 0 deletions src/demo-app/radio/radio-demo.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,4 +11,11 @@ import {MdRadioDispatcher} from '../../components/radio/radio_dispatcher';
})
export class RadioDemo {
isDisabled: boolean = false;
favoriteSeason: string = 'Autumn';
seasonOptions = [
'Winter',
'Spring',
'Summer',
'Autumn',
];
}