Advertisement

#9 Let's Not Forget the Select

By the time we are done with this article we will have created a select form control that we can add to our toolbox. In the process we will also have created a new base class that will make it a little bit easier to create our radio group control latter on. And we will finish with a couple of little styling adjustments so that my OCD will relax a little bit.

Time to create a select control

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

Just like the previous article we will begin by adding a template mixin for our select component (a).

controls.mixins.pug (1)

...
mixin select
    select([formControlName]="tName" )
...
(a) Adding a template mixin for our select component.
  • WebUi
    • Source
      • forms
        • controls
          • select
            • xc-select.component.pug
            • xc-select.component.scss
            • xc-select.component.ts

Our next step is to create the template (b), styling (c), and component definition (d).

xc-select.component.pug (1)

include ../controls.mixins.pug

+formControlGroup
    +select
(b) The template for our select control.

xc-select.component.scss (1)

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

@include formControlGroup;
(c) The styling for our select control.

xc-select.component.ts (1)

import { forwardRef, Component } from "@angular/core";

import { XcFormControl } from "../xc-form-control";

@Component({
    selector: "xc-select",
    template: require("./xc-select.component.pug"),
    styles: [require("./xc-select.component.scss")],
    providers: [{ provide: XcFormControl, useExisting: forwardRef(() => XcSelectComponent) }]
})
export class XcSelectComponent<TValue> extends XcFormControl<TValue> {
    protected get tagName() { return "xc-select"; }
}
(d) The starting component definition for our select control.
  • WebUi
    • Source
      • forms
        • examples
          • form-examples.module.ts
        • controls.index.ts

Once again we need to export our new control from our index file (e) and declare it within our example module definition (f).

controls.index.ts (1)

...
export * from "./select/xc-select.component";
...
(e) Export our select control from our controls index file.

form-examples.module.ts (1)

...
import {
    ..., XcSelectComponent, ...
} from "./form-examples.index";
...
@NgModule({
    ...,
    declarations: [
        ...,
        XcSelectComponent,
        ...
    ],
    ...
})
export class FormExamplesModule { }
platformBrowserDynamic().bootstrapModule(FormExamplesModule);
(f) Declare our select control within our examples module.

Update our basic example

  • WebUi
    • Source
      • forms
        • examples
          • basic
            • basic-example.component.pug
            • basic-example.component.ts

Now we need to add a select control to our basic example. The first step is of course to add it to our template (g) followed by adding a snippet to the basic example component (h).

basic-example.component.pug (1)

...
div.example-container
    div.example-content
        ...
        div.controls-template-component
            xc-form-group(... )
                +controlRow("select")
                    xc-select(label="Select", name="select" )
                ...
(g) Adding a select control to the template of our basic example.

basic-example.component.ts (1)

...
export class BasicExampleComponent implements ... {
    ...
    private readonly _snippets = new SnippetsManager([
        ...
        ["select", { template: 'xc-select(label="select", name="select" )' }],
        ...
    ]);
    ...
}
(h) Adding a snippet to our basic example component.
Advertisement

A select is no good without options

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

Right now adding our select control to the basic example does not throw any errors but of course is pretty useless since it does not contain any options. To add the options we will first modify the template using a *ngFor to loop over a set options and calling a function to convert the option to both a value and a label (i). Next we need to update the component definition to include both the options and the functions needed to convert an option to its value and label (j).

controls.mixins.pug (2)

...
mixin select
    select(... )
        option(*ngFor=`let x of tOptions`, [ngValue]="tOptionValue(x)") {{tOptionLabel(x)}}
...
(i) Updating our template so that our select will display options for the user to select from.

xc-select.component.ts (2)

import { ..., Input } from "@angular/core";
...
export class XcSelectComponent <TValue, TOption> extends ... {
    @Input("options") private readonly _options: TOption[] = null;
    @Input("optionLabel") private readonly _optionLabel = (option: TOption) => { return (option as any).label; }
    @Input("optionValue") private readonly _optionValue = (option: TOption) => { return (option as any).value; }

    private get tOptions() { return this._options; }
    private get tOptionLabel() { return this._optionLabel; }
    private get tOptionValue() { return this._optionValue; }
    ...
}
(j) Adding the getter for our options as well as the functions used to convert an option to its label and value. The user can set these functions but we will provide a default to begin with.

Give us the options

  • WebUi
    • Source
      • forms
        • examples
          • basic
            • basic-example.component.pug
            • basic-example.component.ts

Back to our basic example in order to modify both the template (k) and component (l) in order to provide the options to our select.

basic-example.component.ts (2)

...
export class BasicExampleComponent implements ... {
    ...
    private readonly _snippets = new SnippetsManager([
        ...
        ["select", {
            component: `private get tOptions() {
    return [
        { label: "label1", value: "value1" },
        { label: "label2", value: "value2" },
        { label: "label3", value: "value3" },
        { label: "label4", value: "value4" }
    ];
}`,
            template: 'xc-select(label="Select", name="select", [options]="tOptions" )'
        }],
        ...
    ]);
    ...
    private get tOptions() {
        return [
            { label: "label1", value: "value1" },
            { label: "label2", value: "value2" },
            { label: "label3", value: "value3" },
            { label: "label4", value: "value4" }
        ];
    }
    ...
}
(k) We need to update our basic example component so that it has a getter to provide the options to the template as well as add them to the snippet.

basic-example.component.pug (2)

...
div.example-container
    div.example-content
        ...
        div.controls-template-component
            xc-form-group(... )
                ...
                +controlRow("select")
                    xc-select(..., [options]="tOptions" )
                ...
            ...
(l) Time to bind the options to our select control.

We are going to need to deal with options again

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

The idea of having an option that contains both a label and a value will be need again, namely for a radio group control. So to make our life easier we will remove the appropriate properties from our select control and move them to a base class (m) that the select will now inherit from (n).

xc-options-form-control.ts (1)

import { Input } from "@angular/core";

import { XcFormControl } from "./xc-form-control";

export abstract class XcOptionsFormControl<TValue, TOption> extends XcFormControl<TValue> {
    @Input("options") private readonly _options: TOption[] = null;
    @Input("optionLabel") private readonly _optionLabel = (o: TOption) => { return (o as any).label; }
    @Input("optionValue") private readonly _optionValue = (o: TOption) => { return (o as any).value; }

    private get tOptions() { return this._options; }
    private get tOptionLabel() { return this._optionLabel; }
    private get tOptionValue() { return this._optionValue; }
}
(m) We need a base class that will encapsulate the idea of an option that contains both a value and a label.

xc-select.component.ts (3)

import { forwardRef, Component } from "@angular/core";

import { XcFormControl } from "../xc-form-control";
import { XcOptionsFormControl } from "../xc-options-form-control";

@Component({
    ...
})
export class XcSelectComponent<TValue, TOption> extends XcOptionsFormControl<TValue, TOption> {
    protected get tagName() { return "xc-select"; }
}
(n) Now we just need to make our select inherit from our new base class.

My ocd is kicking in

  • WebUi
    • Source
      • forms
        • examples
          • basic
            • basic-example.component.scss
      • sass
        • 1-base
          • _forms.scss

Depending on how you would be styling your project you may not need to make the following changes. The first change we are going to make is to adjust how our form elements are styled throughout the project (o). The last change is a very small one to how pre elements are styled within our examples (p).

_forms.scss (1)

...
#{$all-text-inputs}, select {
    appearance: none; /* <-- Remove this */
    ...
}

#{$all-text-inputs} {
    appearance: none;
}

select {
    padding: $base-spacing/3-0.15625em $base-spacing/3;
}
...
(o) Time to make our select elements look more like all the other form elements.

basic-example.component.scss (1)

...
.control-row {
    ...
    pre {
        margin-top: 0;
    }
}
...
(p) A small change to the visual appearance of our code samples.
Exciton Interactive LLC
Advertisement