#11 A Radio Group is Useless Without Buttons
Friday, September 21, 2018
In the previous article we created a radio button group which is pretty useless without having any radio button components to go with it. By the end of this article we will have a working radio button group that contains radio buttons which behave the way we expect them to and the radio button group's form control will contain the value for whichever radio button the user has selected.
Parts
- Part 14: Pipes to the Rescue
- Part 13: Rest of the Examples
- Part 12: Checkbox Example
- Part 11: Adding a Radio Button
- Part 10: Adding a Radio Group
- Part 9: Adding a Select
- Part 8: Adding a Checkbox
- Part 7: Adding a Textarea
- Part 6: Highlighting with Prismjs
- Part 5: Form Snippets Manager
- Part 4: Accessing Form Data
- Part 3: Angular Form Group Interop
- Part 2: The Form Group
- Part 1: Forms Project Creation
Time to break with tradition
-
WebUi
-
Source
-
forms
- xc-control.ts
- xc-form-control.ts
-
forms
-
Source
Since we have started each of the previous several articles by creating a template for our current control
under consideration you would be forgiven for thinking that is how we are going to start this article. For this
one though our first step is to modify our base classes. In this instance it is a small change and it only
amounts to removing the tagName
getter from the form control
(a) and moving it to the control (b).
The reason for this we will see shortly.
xc-form-control.ts (1)
...
export abstract class XcFormControl ... {
...
protected abstract get tagName(): string; // <-- Remove this
...
}
xc-control.ts (1)
...
export abstract class XcControl {
...
protected abstract get tagName(): string;
}
Now we are back on track
-
WebUi
-
Source
-
forms
- controls.mixins.pug
-
forms
-
Source
Time to create the template for our radio button (c). To do this though we have to do a bit more typing since it is the radio group that will contain the form control.
controls.mixins.pug (3)
...
mixin radio
div.xc-form-control-group
label(*ngIf="tLabel" ) {{tLabel}}
input(type="radio" )
...
Once again time to create our control
-
WebUi
-
Source
-
forms
-
controls
-
radio
- xc-radio.component.pug
- xc-radio.component.scss
- xc-radio.component.ts
-
radio
-
controls
-
forms
-
Source
As usual we need to create our template file (d), the styling file
(e) and the component definition (f).
Do take note that the component inherits from XcControl
and not from
XcFormControl
. We also do not have to deal with the
forwardRef
function since the radio group will be handling the
buttons directly.
xc-radio.component.pug (1)
include ../controls.mixins.pug
+radio
xc-radio.component.scss (1)
@import "../controls.mixins.scss";
@include formControlGroup;
xc-radio.component.ts (1)
import { Component } from "@angular/core";
import { XcControl } from "../xc-control";
@Component({
selector: "xc-radio",
template: require("./xc-radio.component.pug"),
styles: [require("./xc-radio.component.scss")]
})
export class XcRadioComponent<TValue> extends XcControl {
private _value: TValue = null;
protected get tagName() { return "xc-radio"; }
public get value() { return this._value; }
public set value(value: TValue) { this._value = value; }
}
You guessed it...time to export
-
WebUi
-
Source
-
forms
-
examples
- form-examples.module.ts
- controls.index.ts
-
examples
-
forms
-
Source
Next up is exporting our radio button component (g) and declaring it within the form examples module (h).
controls.index.ts (2)
...
export * from "./radio/xc-radio.component";
...
form-examples.module.ts (2)
...
import {
..., XcRadioComponent, ...
} from "./form-examples.index";
...
@NgModule({
...,
declarations: [
...,
XcRadioComponent,
...
],
entryComponents: [
XcRadioComponent
],
...
})
export class FormExamplesModule { }
platformBrowserDynamic().bootstrapModule(FormExamplesModule);
Let's create a radio button
-
WebUi
-
Source
-
forms
-
controls
-
radio
- xc-radio-group.component.ts
-
radio
- xc-control.ts
- xc-options-form-control.ts
-
controls
-
forms
-
Source
Since we are going to be creating the radio buttons ourselves we need to be able to set the label (i), and we need access to the options within the form group component (j). Now that we have access to everything that we need we can now actually create and insert our radio buttons into the DOM from our radio button group component (k).
xc-control.ts (2)
...
export abstract class XcControl {
@Input("label") private _label: string = null; // <-- Remove readonly
...
public set label(value: string) { this._label = value; }
}
xc-options-form-control.ts (1)
...
export abstract class XcOptionsFormControl ... {
...
protected get tOptions() { return this._options; } // <-- Changed visibility from private
protected get tOptionLabel() { return this._optionLabel; } // <-- Changed visibility from private
protected get tOptionValue() { return this._optionValue; } // <-- Changed visibility from private
}
xc-radio-group.component.ts (1)
import { ..., ComponentFactory, ComponentFactoryResolver, ComponentRef, ... } from "@angular/core";
import { from } from "rxjs";
import { map } from "rxjs/operators";
...
import { XcRadioComponent } from "./xc-radio.component";
...
export class XcRadioGroupComponent ... {
...
private readonly _radioFactory: ComponentFactory<XcRadioComponent<TValue>> = null;
private readonly _radios: ComponentRef<XcRadioComponent<TValue>>[] = [];
...
constructor(@Inject(ComponentFactoryResolver) private readonly _cfr: ComponentFactoryResolver) {
super();
this._radioFactory = this._cfr.resolveComponentFactory<XcRadioComponent<TValue>>(XcRadioComponent);
}
...
private createRadioComponents = () => {
if(typeof this._radioContainer === "undefined" || this._radioContainer === null) {
...
}
from(this.tOptions)
.pipe(
map(x => {
const compRef = this._radioFactory.create(this._radioContainer.parentInjector);
const instance = compRef.instance;
instance.label = this.tOptionLabel(x);
instance.value = this.tOptionValue(x);
this._radios.push(compRef);
return compRef;
})
)
.subscribe(x => this._radioContainer.insert(x.hostView));
}
}
We see them but we need them to behave correctly
-
WebUi
-
Source
-
forms
-
controls
-
radio
- xc-radio.component.ts
- controls.mixins.pug
-
radio
-
controls
-
forms
-
Source
Now that we have inserted the radio buttons we need to work on them behaving the way we expect them to. To do this we will first modify the template so that we can modify their checked property as well as know when the user has clicked on one of them (l).
controls.mixins.pug (2)
...
mixin radio
div.xc-form-control-group
label ...
input(..., [checked]="tChecked", (click)="tOnClick()" )
...
xc-radio.component.ts (2)
...
export class XcRadioComponent ... {
private _checked: boolean = false;
private _onClick: (x: XcRadioComponent<TValue>) => void = null;
private get tChecked() { return this._checked; }
...
public set checked(value: boolean) { this._checked = value; }
public set onClick(value: (x: XcRadioComponent<TValue>) => void) { this._onClick = value; }
...
private tOnClick = () => {
this._onClick(this);
}
}
Once again back to the radio button group
-
WebUi
-
Source
-
forms
-
controls
-
radio
- xc-radio-group.component.ts
-
radio
-
controls
-
forms
-
Source
Now it is just a matter of setting the onClick
callback for each
button (n) and again using a bit of rxjs magic.
xc-radio-group.component.ts (2)
...
import { filter, ..., tap } from "rxjs/operators";
...
export class XcRadioGroupComponent ...{
...
private createRadioComponents = () => {
...
from(this._options)
.pipe(
map(x => {
const compRef = this._radioFactory.create(this._radioContainer.parentInjector);
...
instance.onClick = this.onClick;
...
})
)
.subscribe(...);
}
private onClick = (r: XcRadioComponent<TValue>) => {
from(this._radios)
.pipe(
map(x => x.instance),
tap(x => x.checked = x === r),
filter(x => x === r)
)
.subscribe(x => {
this.value = x.value
});
}
}