Advertisement

#11 A Radio Group is Useless Without Buttons

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.

Time to break with tradition

  • WebUi
    • Source
      • forms
        • xc-control.ts
        • xc-form-control.ts

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
    ...
}
(a) Removing the tagName getter from the form control.

xc-control.ts (1)

...
export abstract class XcControl {
    ...
    protected abstract get tagName(): string;
}
(b) Adding the tagName getter to the control.

Now we are back on track

  • WebUi
    • Source
      • forms
        • controls.mixins.pug

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" )
...
(c) Adding the template for our radio button component.

Once again time to create our control

  • WebUi
    • Source
      • forms
        • controls
          • radio
            • xc-radio.component.pug
            • xc-radio.component.scss
            • xc-radio.component.ts

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
(d) Adding the template file.

xc-radio.component.scss (1)

@import "../controls.mixins.scss";

@include formControlGroup;
(e) Adding the styling file.

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; }
}
(f) Adding the component definition.
Advertisement

You guessed it...time to export

  • WebUi
    • Source
      • forms
        • examples
          • form-examples.module.ts
        • controls.index.ts

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";
...
(g) Exporting the radio button from our controls index file.

form-examples.module.ts (2)

...
import {
    ..., XcRadioComponent, ...
} from "./form-examples.index";
...
@NgModule({
    ...,
    declarations: [
        ...,
        XcRadioComponent,
        ...
    ],
    entryComponents: [
        XcRadioComponent
    ],
    ...
})
export class FormExamplesModule { }
platformBrowserDynamic().bootstrapModule(FormExamplesModule);
(h) Declaring the radio button component within our form examples module.

Let's create a radio button

  • WebUi
    • Source
      • forms
        • controls
          • radio
            • xc-radio-group.component.ts
        • xc-control.ts
        • xc-options-form-control.ts

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; }
}
(i) We need to able to set the label of our radio buttons.

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
}
(j) In order to know what radio buttons we need to add we need access to the options within our radio button group.

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));
    }
}
(k) Time to use a little rxjs magic to create and insert our buttons within the DOM.

We see them but we need them to behave correctly

  • WebUi
    • Source
      • forms
        • controls
          • radio
            • xc-radio.component.ts
          • controls.mixins.pug

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()" )
...
(l) Modifying the template so that we can change the checked property and know when a user has clicked on a button.

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);
    }
}
(m) Providing a way for the form group to control the checked state through the use of the onClick callback.

Once again back to the radio button group

  • WebUi
    • Source
      • forms
        • controls
          • radio
            • xc-radio-group.component.ts

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
            });
    }
}
(n) Setting the onClick callback and using rxjs to set the checked state of each button and the value of the form control.
Exciton Interactive LLC
Advertisement