Advertisement

#12 Adding a Checkbox Example

By the time we are done with this article we will have created a base class, a pug mixin, and a scss mixin that will make it very easy for us to add example components for each of our controls. We will also have used these tools to create an additional example component for our checkbox control.

An eye to improving change detection in the future

  • WebUi
    • Source
      • package.json

We are going to start by adding a package to our project. The main reason we are doing this is to have access to immutable data structures that we can use to improve how change detection is handled in our project. Another readon is just to learn more about a popular javascript package.

npm install (1)

npm install --save-dev immutable
(a) Time to add a new package to our project.

package.json (1)

{
    ...,
    "devDependencies": {
        ...
        "immutable": "^3.8.2",
        ...
    }
}
(b) At the time of the writing of this article immutable.js is on version 3.8.2.

Updating our snippets manager

  • WebUi
    • Source
      • forms
        • snippets-manager.ts

The first order of business is to make some changes to our snippets manager. As it stands now we are creating the snippets manager within our basic example component. Since we will be refactoring as much as we can into a base class we need to provide a way to add snippets to the manager after it has been created. To do this we will convert the internal snippets from a readonly only javascript map to an immutable map and provide a method for adding snippets (c). With this done we need to adjust how the internal map is converted to an observable. While we are at it we are going to update the snippetsToCode method processes our strings by only allowing distinct items to continue through the process.

snippets-manager.ts (1)


import { Map } from "immutable";

import { ..., pairs, ... } from "rxjs";
import { distinct, ... } from "rxjs/operators";

export type Snippet = { component?: string; template: string; };
export type SnippetArray = Array<[string, Snippet]>;

export class SnippetsManager {
    private _snippets: Map<string, Snippet> = null; <-- Remove readonly

    constructor(snippets: Array<[string, Snippet]>) { <-- Remove constructor
        this._snippets = new Map(snippets);
    }

    public addSnippets = (snippets: SnippetArray) => {
        this._snippets = new Map(snippets);
    }
     
    public getComponent = (key: string) => {
        const snippet = this._snippets.get(key);
        if(typeof snippet === "undefined" || snippet === null) {
            return `missing snippet for ${key}`;
        }
        return snippet.component || "// none required";
    }

    public getTemplate = (key: string) => {
        const snippet = this._snippets.get(key);
        if(typeof snippet === "undefined" || snippet === null) {
            return `missing snippet for ${key}`;
        }
        return snippet.template || `missing template for ${key}`;
    }

    ...
    private snippetsToCode = (indent: number, f: (s: Snippet) => string) => {
        let res: string;
        pairs(this._snippets.toObject())
            .pipe(
                map((x: [string, Snippet]) => this.indent(indent, f(x[1]))),
                distinct(x => x),
                filter( ... ),
                ...
            )
            .subscribe( ... );
        return res;
    }
    ...
}
(c) Updating our snippets manager to allow the adding of snippets after the manager has been created and to convert those snippets to an immutable map.

The base class that all example component will inherit from

  • WebUi
    • Source
      • forms
        • examples
          • examples.component.ts
          • examples.mixins.scss
          • examples.mixins.pug

Now it is time to create the class and corresponding mixins that we need in order to make creating additional example components as easy as possible. We will start by creating a pug mixin for the templates (d). With this mixin all we have to do is provide the controls that are relevant to a particular example. Next up is the styling. This is even easier since all we have to do is create a mixin and copy over all of the code from the basic example's style file (e). Last up is creating the base typescript class (f).

examples.mixins.pug

mixin example
    div.example-container
        div.example-content
            div.form-data
                div.form-data-heading
                    h2 Form
                    div.validity.valid(*ngIf="tValid" ) Valid
                    div.validity.invalid(*ngIf="tInvalid" ) Invalid
                h3.form-output-heading Data
                div.form-data-output {{tData}}
                hr
                h3.form-output-heading Errors
                div.form-data-output {{tErrors}}
            div.controls-template-component
                xc-form-group(#form="", name="Form" )
                    block
                hr
                div.template-component
                    div.template
                        label Template
                        pre
                            code.language-pug {{tTemplate}}
                    div.component
                        label Component
                        pre
                            code.language-typescript {{tComponent}}
...
(d) Creating a pug mixin that all example components can use for their template.

examples.mixins.scss (1)

@mixin example {
    .example-container {
        display: flex;
        height: 100%;
    }
    
    .example-content {
        display: grid;
        grid-template-columns: auto 1fr;
        grid-template-areas: "form-data controls";
        flex: 1;
    }
    
    $gutter: 0.25em;
    $form-data-width: 13em;
    $form-data-background-color: white;
    $form-data-border-color: gray;
    
    .form-data {
        grid-area: form-data;
        width: $form-data-width + 2*$gutter;
        border-right: 1px solid $form-data-border-color;
        background-color: $form-data-background-color;
        padding: $gutter;
    
        .form-data-heading {
            border-bottom: 1px solid $form-data-border-color;
            display: flex;
            flex-direction: row;
            align-items: center;
            padding-bottom: 0.25em;
            margin-bottom: 1.5em;
    
            h2 {
                flex: 1;
                margin: 0;
            }
    
            .validity {
                padding: 0.25em;
                color: white;
                width: 3.5em;
                text-align: center;
    
                &.valid {
                    background-color: #34a934;
                }
    
                &.invalid {
                    background-color: #9a2a2a;
                }
            }
        }
    
        .form-output-heading {
            margin: 0;
            font-size: 1.15em;
        }
    
        .form-data-output {
            background-color: $form-data-background-color;
            border: none;
            box-shadow: none;
            overflow: auto;
            white-space: pre;
            margin-bottom: 0.25em;
        }
    }
    
    .controls-template-component {
        grid-area: controls;
        padding: $gutter;
        overflow: auto;
    }
    
    .control-row {
        display: grid;
        grid-template-columns: auto 1fr 1fr;
        grid-template-areas: "control template component";
        grid-column-gap: $gutter;
    
        .control {
            grid-area: control;
            width: 18.75em;
        }
    
        .component {
            grid-area: component;
        }
    
        .template {
            grid-area: template;
        }
    
        pre {
            margin-top: 0;
        }
    }
    
    .template-component {
        display: grid;
        grid-template-columns: 1fr 1fr;
        grid-template-areas: "template component";
        grid-column-gap: $gutter;
    
        .component {
            grid-area: component;
        }
    
        .template {
            grid-area: template;
        }
    }
}
(e) Creating a scss mixin that all example components can use for their styling.

examples.component.ts (1)

import { AfterViewInit, ViewChild } from "@angular/core";

import { SnippetArray, SnippetsManager, SyntaxHighlightingService, XcFormGroupComponent } from "../forms.index";

export abstract class ExamplesComponent implements AfterViewInit {
    @ViewChild("form") private readonly _form: XcFormGroupComponent = null;

    private get tData() { return JSON.stringify(this._form.data, null, 2); }
    private get tErrors() { return JSON.stringify(this._form.errors, null, 2); }
    private get tInvalid() { return this._form.invalid; }
    private get tValid() { return this._form.valid; }

    protected readonly snippetsManager: SnippetsManager = new SnippetsManager();

    protected abstract get snippets(): SnippetArray;

    protected abstract get tComponent(): string;
    protected abstract get tTemplate(): string;

    constructor(private readonly _syntaxHighlighter: SyntaxHighlightingService) { }
    
    public ngAfterViewInit(): void {
        this._syntaxHighlighter.highlightAll();
    }

    protected addSnippets = (snippets: SnippetArray) => {
        this.snippetsManager = new SnippetsManager(snippets);
    }

    private tGetSnippetComponent = (key: string) => {
        return this.snippetsManager.getComponent(key);
    }

    private tGetSnippetTemplate = (key: string) => {
        return this.snippetsManager.getTemplate(key);
    }
}
(f) Creating the base class that all example components can inherit from.
Advertisement

Updating our basic example

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

Now it's time to put to work everything that we did in the previous section. We will start by updating the template by using the pug mixin (g). Next up, and easiest of all, is the scss changes (h). Both of these are the entire contents of their respective files. Now it's time to deal with the component definition (i).

basic-example.component.pug (1)

include ../examples.mixins.pug

+example
    +controlRow("checkbox")
        xc-checkbox(label="Checkbox", name="checkbox")
    +controlRow("input")
        xc-input(label="Input", name="input" )
    +controlRow("radio")
        xc-radio-group(label="Radio", name="radio", [options]="tOptions")
    +controlRow("select")
        xc-select(label="Select", name="select", [options]="tOptions")
    +controlRow("textarea")
        xc-textarea(label="Textarea", name="textarea" )
(g) Updating the component template file.

basic-example.component.scss (1)

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

@include example;
(h) Updating the component style file.

basic-example.component.ts (1)

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

import { ExamplesComponent } from "../examples.component";
import { SnippetArray, SyntaxHighlightingService } from "../../forms.index";

@Component({
    selector: "basic-example",
    template: require("./basic-example.component.pug"),
    styles: [require("./basic-example.component.scss")]
})
export class BasicExampleComponent extends ExamplesComponent {
    protected get snippets(): SnippetArray {
        return [
        ["checkbox", { template: 'xc-checkbox(label="Checkbox", name="checkbox")' }],
        ["input", { template: 'xc-input(label="Input", name="input" )' }],
        ["radio", {
            component: `private get tOptions() {
    return [
        { label: "label1", value: "value1" },
        { label: "label2", value: "value2" },
        { label: "label3", value: "value3" },
        { label: "label4", value: "value4" }
    ];
}`,
            template: 'xc-radio-group(label="Radio", name="radio", [options]="tOptions" )'
        }],
        ["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")' 
        }],
        ["textarea", { template: 'xc-textarea(label="Textarea", name="textarea" )' }]
    ]; 
    }
    protected get tComponent() {
        return `import { Component } from "@angular/core";

import { XcFormGroupComponent } from "../../forms.index";
@Component({
    selector: "basic-example",
    template: require("./basic-example.component.pug"),
    styles: [require("./basic-example.component.scss")]
})
export class BasicExampleComponent {
${this.snippetsManager.snippetsToComponent()}
}`;
    }
    private get tOptions() {
        return [
            { label: "label1", value: "value1" },
            { label: "label2", value: "value2" },
            { label: "label3", value: "value3" },
            { label: "label4", value: "value4" }
        ];
    }
    protected get tTemplate() {
        return `xc-form-group(name="Form" )
${this.snippetsManager.snippetsToTemplate(1)}`;
    }

    constructor(@Inject(SyntaxHighlightingService) syntaxHighlighter: SyntaxHighlightingService) {
        super(syntaxHighlighter);

        this.addSnippets(this.snippets);
    }
}
(i) Updating the component definition file.

How easy is it to make a new example component?

  • WebUi
    • Source
      • forms
        • examples
          • checkbox
            • checkbox-example.component.pug
            • checkbox-example.component.scss
            • checkbox-example.component.ts

With the refactoring of our basic example done we now turn to creating a new example component for our checkbox control. It is now a pretty easier affair to create it's template (j), styling (k), and component definition (l).

checkbox-example.component.pug (1)

include ../examples.mixins.pug

+example
    +controlRow("checkbox")
        xc-checkbox(label="Checkbox", name="checkbox")
(j) Adding the template for our checkbox example.

checkbox-example.component.scss (1)

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

@include example;
(k) Adding the styling for our checkbox example.

checkbox-example.component.ts (1)

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

import { ExampleComponent } from "../example.component";
import { SnippetArray, SyntaxHighlightingService } from "../../forms.index";

@Component({
    selector: "checkbox-example",
    template: require("./checkbox-example.component.pug"),
    styles: [require("./checkbox-example.component.scss")]
})
export class CheckboxExampleComponent extends ExampleComponent {
    protected get snippets(): SnippetArray {
        return [
            ["checkbox", { template: 'xc-checkbox(label="Checkbox", name="checkbox")' }]
        ]; 
    }
    protected get tComponent() {
        return `import { Component } from "@angular/core";

import { XcFormGroupComponent } from "../../forms.index";
@Component({
    selector: "checkbox-example",
    template: require("./checkbox-example.component.pug"),
    styles: [require("./checkbox-example.component.scss")]
})
export class CheckboxExampleComponent {
${this.snippetsManager.snippetsToComponent()}
}`;
    }
    protected get tTemplate() {
        return `xc-form-group.form-group(name="Form" )
${this.snippetsManager.snippetsToTemplate(1)}`;
    }

    constructor(@Inject(SyntaxHighlightingService) syntaxHighlighter: SyntaxHighlightingService) {
        super(syntaxHighlighter);

        this.addSnippets(this.snippets);
    }
}
(l) Adding the component defition for our checkbox example.

Can't use it till it's registered

  • WebUi
    • Source
      • forms
        • examples
          • form-examples.component.pug
          • form-examples.index.ts
          • form-examples.module.ts

With the checkbox example created we need to register it with our module. First up, of course, is to export it from our examples index file (m). Next we will update our links to contain an anchor tag that will point to our new example component (n). And last but not least the actual creating of the route and registering with the module (o).

form-examples.index.ts (1)

...
export * from "./checkbox/checkbox-example.component";
...
(m) Export the checkbox example from our example index.

form-examples.component.pug (1)

nav.main-nav
    ul
        li
            a(routerLink="/basic", routerLinkActive="active-link") Basic
        li
            a(routerLink="/checkbox", routerLinkActive="active-link") Checkbox

div.router-outlet
    router-outlet
(n) Create a router link for the checkbox example.

form-examples.module.ts (1)

...
import {
    ..., CheckboxExampleComponent, ...
} from "./form-examples.index";

const routes: Routes = [
    ...,
    {
        path: "checkbox",
        component: CheckboxExampleComponent
    }
];

@NgModule({
    ...,
    declarations: [
        ...,
        CheckboxExampleComponent,
        ...
    ],
    ...
})
export class FormExamplesModule { }
platformBrowserDynamic().bootstrapModule(FormExamplesModule);
(o) Creat the route and add the checkbox example component to our component declarations.

Now that we have an easy way to create example components I will add an example for the rest of our controls and we will pick up from there in the next article.

Exciton Interactive LLC
Advertisement