Skip to content

Commit

Permalink
#52: fix radio buttons for reactive forms
Browse files Browse the repository at this point in the history
  • Loading branch information
mseemann committed Aug 30, 2016
1 parent a6415bf commit b417de3
Show file tree
Hide file tree
Showing 3 changed files with 117 additions and 14 deletions.
8 changes: 8 additions & 0 deletions src/app/reactiveforms/reactiveform.component.html
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,14 @@ <h4>Reactive Forms Example</h4>
<p>
<mdl-textfield label="Last Name" name="lastname" type="text" formControlName="lastName" floating-label></mdl-textfield>
</p>
<p>
<mdl-radio name="x" value="1" formControlName="x" mdl-ripple>Value 1</mdl-radio>
<mdl-radio name="x" value="2" formControlName="x" mdl-ripple>Value 2</mdl-radio>
</p>
<p>
<input name="x" value="1" formControlName="x" type="radio">Value 1
<input name="x" value="2" formControlName="x" type="radio">Value 2
</p>
<p>
<mdl-textfield
label="Email"
Expand Down
4 changes: 3 additions & 1 deletion src/app/reactiveforms/reactiveform.component.ts
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,7 @@ export class ReactiveFormsDemo extends AbstractDemoComponent implements OnInit {
public lastName = new FormControl('', Validators.required);
public email = new FormControl('', emailValidator);
public email2 = new FormControl('', emailValidator);
public x = new FormControl('1');

constructor(router: Router, route: ActivatedRoute, titleService: Title, private fb: FormBuilder) {
super(router, route, titleService);
Expand All @@ -48,7 +49,8 @@ export class ReactiveFormsDemo extends AbstractDemoComponent implements OnInit {
'firstName': this.firstName,
'lastName': this.lastName,
'email': this.email,
'email2': this.email2
'email2': this.email2,
'x': this.x
});
this.form.valueChanges
.map((formValues) => {
Expand Down
119 changes: 106 additions & 13 deletions src/components/radio/mdl-radio.component.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,14 +5,25 @@ import {
Renderer,
forwardRef,
Input,
NgModule
NgModule,
DoCheck,
OnInit,
Injectable,
OnDestroy,
Optional,
Inject,
Self
} from '@angular/core';
import {
NG_VALUE_ACCESSOR,
ControlValueAccessor,
FormsModule
FormsModule,
FormControl,
FormControlName,
FormGroup,
NgModel
} from '@angular/forms';
import { CommonModule } from '@angular/common';
import {CommonModule} from '@angular/common';


const MD_INPUT_CONTROL_VALUE_ACCESSOR = new Provider(NG_VALUE_ACCESSOR, {
Expand All @@ -23,6 +34,35 @@ const MD_INPUT_CONTROL_VALUE_ACCESSOR = new Provider(NG_VALUE_ACCESSOR, {
const noop = () => {};
const IS_FOCUSED = 'is-focused';

// Registry for mdl-readio compnents. Is responsible to keep the
// right state of the radio buttons of a radio group. It would be
// easier if i had a mdl-radio-group component. but this would be
// a big braking change.
@Injectable()
export class MdlRadioGroupRegisty {

private radioComponents: any[] = [];

public add(radioComponent: MdlRadioComponent) {
this.radioComponents.push(radioComponent);
}

public remove(radioComponent: MdlRadioComponent) {
this.radioComponents.slice(this.radioComponents.indexOf(radioComponent), 1);
}

public select(radioComponent: MdlRadioComponent) {
// unselect evenry radioComponent that is not the provided radiocomponent and has the same name
this.radioComponents.forEach( (component) => {
if (component.name === radioComponent.name) {
if (component !== radioComponent){
component.deselect(radioComponent.value);
}
}
});
}
}

/*
<mdl-radio name="group1" value="1" [(ngModel)]="radioOption">Value 1</mdl-radio>
*/
Expand All @@ -34,11 +74,11 @@ const IS_FOCUSED = 'is-focused';
'(click)': 'onClick()',
'[class.mdl-radio]': 'true',
'[class.is-upgraded]': 'true',
'[class.is-checked]': 'optionValue === value'
'[class.is-checked]': 'checked'
},
template: `
<input type="radio" class="mdl-radio__button"
name="{{name}}"
<input type="checkbox" class="mdl-radio__buttonXXXXXXX"
[attr.name]="name"
(focus)="onFocus()"
(blur)="onBlur()"
[(ngModel)]="checked">
Expand All @@ -47,27 +87,58 @@ const IS_FOCUSED = 'is-focused';
<span class="mdl-radio__inner-circle"></span>
`
})
export class MdlRadioComponent implements ControlValueAccessor {
export class MdlRadioComponent implements ControlValueAccessor, OnInit, OnDestroy {

@Input() public name: string;
@Input() public formControlName: string;

@Input() public value: any;
@Input() public optionValue: any;
public optionValue: any;
// the internal state - used to set the underlaying radio button state.
public checked = false;

private el: HTMLElement;
private onTouchedCallback: () => void = noop;
private onChangeCallback: () => void = noop;


constructor(private elementRef: ElementRef, private renderer: Renderer) {
constructor(
private elementRef: ElementRef,
private renderer: Renderer,
private ragioGroupRegisty: MdlRadioGroupRegisty) {
this.el = elementRef.nativeElement;
}

public ngOnInit() {
// we need a name and it must be the same as in the formcontrol.
// a radio group without name is useless.
this.checkName();
// register the radio button - this is the only chance to unselect the
// radio button that is no longer active
this.ragioGroupRegisty.add(this);
}

public ngOnDestroy() {
this.ragioGroupRegisty.remove(this);
}

public writeValue(optionValue: any): void {
this.optionValue = optionValue;
this.updateCheckState();
}

private onTouchedCallback: () => void = noop;
private onChangeCallback: (_: any) => void = noop;
public deselect(value: any) {
// called from the registry. the value is the value of the selected radio button
// e.g. the radio button get unselected if it isnÄt the selected one.
this.writeValue(value);
}

public registerOnChange(fn: any): void {
this.onChangeCallback = fn;
// wrap the callback, so that we can call select on the registry
this.onChangeCallback = () => {
fn(this.value);
this.ragioGroupRegisty.select(this);
};
}

public registerOnTouched(fn: any): void {
Expand All @@ -84,7 +155,28 @@ export class MdlRadioComponent implements ControlValueAccessor {

protected onClick() {
this.optionValue = this.value;
this.onChangeCallback(this.value);
this.updateCheckState();
this.onChangeCallback();
}

private updateCheckState() {
this.checked = this.optionValue === this.value;
}

private checkName(): void {
if (this.name && this.formControlName && this.name !== this.formControlName) {
this.throwNameError();
}
if (!this.name && this.formControlName) {
this.name = this.formControlName;
}
}

private throwNameError(): void {
throw new Error(`
If you define both a name and a formControlName attribute on your radio button, their values
must match. Ex: <mdl-radio formControlName="food" name="food"></mdl-radio>
`);
}
}

Expand All @@ -95,6 +187,7 @@ export const MDL_RADIO_DIRECTIVES = [MdlRadioComponent];
@NgModule({
imports: [CommonModule, FormsModule],
exports: MDL_RADIO_DIRECTIVES,
providers: [MdlRadioGroupRegisty],
declarations: MDL_RADIO_DIRECTIVES,
})
export class MdlRadioModule {}

0 comments on commit b417de3

Please sign in to comment.