Advertisement

#2 Creating the Form Group and Our First Control

In this article we will get our form group up and running as well as our first control. We will also add several index files to make importing all of our components easier and a core file to contain all elements that will need access to in every file.

Cleanup on aisle 8

  • WebUi
    • Source
      • forms
        • examples
          • basic
            • basic-example.component.scss
          • form-examples.component.scss

We will start by just cleaning a bit from setting up the project by removing the styles in our form examples (a) and basic example (b) components..

form-examples.component.scss

.main-nav {
    background-color: lightblue; // Remove this
}
(a) Time to remove this test style.

basic-example.component.scss (1)

h1 { // Remove this
    color: red;
}
(b) Time to remove this test style.

Defining a form group

  • WebUi
    • Source
      • forms
        • xc-form-group.component.pug
        • xc-form-group.component.ts

For our project every form is contained within a form group component so let us define what a form group is. We start with the template (c) where we will bind an instance of angular's form group to the formGroup property and use the ng-content tag so that any components that are placed within our form group will be rendered. Next we need to define the form group component (d). As a note here I have not completely made my mind up yet but I am trying out an idea for specifying when a property is needed for the component template. In order to do that I am prepending a 't' to the property name. So in this case I can set the visibility of the tGroup property to private and still know that I cannot remove it since it is part of the template.

xc-form-group.component.pug (1)

form([formGroup]="tGroup")
    ng-content
(c) Defining the form group template where we are binding to angular's form group property.

xc-form-group.component.ts (1)

import { Component } from "@angular/core";
import { FormGroup } from "@angular/forms";

@Component({
    selector: "xc-form-group",
    template: require("./xc-form-group.component.pug")
})
export class XcFormGroupComponent {
    private readonly _tGroup: FormGroup = null;

    private get tGroup() { return this._tGroup; }

    constructor() {
        this._tGroup = new FormGroup({});
    }
}
(d) Defining the form group component.

All the cool kids have core namespace

  • WebUi
    • Source
      • forms
        • forms.core.ts
        • forms.index.ts

We need a place where we can define interfaces, enums and functions that we need throughout our project. To this we will define a core file (e). To start with we will simply export our form group component since the project will not work without it. Next we will define an index file which will simply export everything thing from our forms project (f) except for anything contained within the examples folder.

forms.core.ts (1)

export * from "./xc-form-group.component";
(e) We need a place where we can store the core elements of our forms project.

forms.index.ts (1)

export * from "./forms.core";
(f) The index file will just export everything from our project that is not contained within our examples folder.

Add the form group to our module

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

Now that we have an index file that will export everything outside of our examples folder we can export its contents from our form examples index file (g). This will allow us to then import everything into our examples module without having to list a bunch of import locations. Just keep things a little more organized we will import everything from the examples folder from one statement and everything else from another (h).

form-examples.index.ts (1)

...
export * from "../forms.index";
(g) Exporting the forms index file from here will allow us to just import everyting we need in the module from this file.

form-examples.module.ts (1)

...
import {
    XcFormGroupComponent
} from "./form-examples.index";
...
@NgModule({
    bootstrap: ...,
    declarations: [
        ...,
        XcFormGroupComponent
    ],
    exports: ...,
    imports: ...
})
export class FormExamplesModule { }
...
(h) We are going to add a second import statement which will bring in everything that is not in the examples folder just to keep things a little more organized.

Make sure that an input is set

  • WebUi
    • Source
      • forms
        • examples
          • basic
            • basic-example.component.pug
        • xc-form-group.component.ts
        • forms.core.ts

In just about every component we are going to want the user to pass in some information. Some of them will be optional but some of them will be required. In order to make sure that a particular input is set we will define a function that will display an error if it is not (i). The function takes in an input, just to determine if it is undefined or null, and uses the tag name, message and input example to create an error message that is included with the thrown error. We will make use of this function within the form group component to make sure that the user sets the name (j). If the name is not set we will see (k) shown in our console. Since we do not want to just stare at this error all day we need to update our basic example component (l). We will use this name later on to help construct an id for our controls.

forms.core.ts (2)

...
/**
 * If the provided input is null or undefined then an error is thrown. The message of the error follows the
 * format [${tagName}] - ${message} <${tagName} ${inputExample}></${tagName}>
 * @param input
 * @param tagName
 * @param message
 * @param inputExample
 */
export function throwMissingInputError(input: any, tagName: string, message: string, inputExample: string) {
    if (typeof input !== "undefined" && input !== null) {
        return;
    }
    const m = `[${tagName}] - ${message} <${tagName} ${inputExample}></${tagName}>`;
    throw new Error(m);
}
(i) Including a fuction within our core file that we can use to require that the user set an input.

xc-form-group.component.ts (2)

import { AfterViewInit, ..., Input } from "@angular/core";
...
import { throwMissingInputError } from "./forms.core";
...
export class XcFormGroupComponent implements AfterViewInit {
    @Input("name") private readonly _name: string = null;
    ...
    public ngAfterViewInit(): void {
        throwMissingInputError(this._name, "xc-form-group", "The name of the form group must be set.", "name='name'");
    }
}
(m) We are adding a name input to our form group so that we can use it later on to help create a unique id for our controls. After the view is initialized we will make sure that the user has set the name input.

basic-examples.component.pug (1)

xc-form-group(name="Basic Form Group")
(l) We need to update our basic example so that our form group has a name.
Image shows what the error looks like if the name input of our form group is not set.
(k) Image shows what the error looks like if the name input of our form group is not set.
Advertisement

A form is no good without controls

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

Now we turn to creating controls for our forms. Before we get to concrete examples we are going to define a couple of abstract classes that our controls can inherit from. We will start with the most fundamental which is the control (n). After that we will define the form control (o) which inherits from control. We will se later on why were are doing this but it relates to how we will handle radio buttons. With these classes created we will export them from our forms core file (p).

xc-control.ts (1)

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

export abstract class XcControl {
    @Input("label") private readonly _label: string = null;

    private get tLabel() { return this._label; }
}
(n) Creating the base class that our forms control class will inherit from.

xc-form-control.ts (1)

import { XcControl } from "../forms.core";

export abstract class XcFormControl extends XcControl {

}
(o) Creating the base class that all controls will inherit from.

forms.core.ts (3)

...
export * from "./controls/xc-control";
export * from "./controls/xc-form-control";
...
(p) Don't forget to export these classes from our core file.

Our first form control

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

Not surprisingly we will start by creating an input control. For our purpose we will be using this for text inputs. Both checkboxes and radio buttons will have their own control. We will start by creating a simple template which allows us to specify the label and type of the input (q). We will just define an empty stylesheet for now (r). In the component (s) we will handle the input for the type. As you can see we will default to a text input if the type is not specified.

xc-input.component.pug (1)

div.xc-form-control-group
    label(*ngIf="tLabel") {{tLabel}}
    input([type]="tType")
(q) In the template we will bind to the label property, which is provided by the base control, and the type for the input.

xc-input.component.scss (1)

.xc-form-control-group {
    
}
(r) For now we will just have an empty stylesheet.

xc-input.component.ts (1)

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

import { XcFormControl } from "../../forms.core";

@Component({
    selector: "xc-input",
    template: require("./xc-input.component.pug"),
    styles: [require("./xc-input.component.scss")]
})
export class XcInputComponent extends XcFormControl {
    @Input("type") private readonly _tType: string = null;

    private get tType() {
        return typeof this._tType === "undefined" || this._tType === null
            ? "text"
            : this._tType;
    }
}
(s) In our component we will default the type of the input to text unless the user specifies otherwise.

Everything can be solved with another index file

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

To make life easier we are going to define another index file. This time we will export all of the controls that we create (t). We do not want to forget to add a reference to this file in our form index file (u). With that done we can return to our module and add a declaration for the input (v).

controls.index.ts (1)

export * from "./input/xc-input.component";
(t) The controls index file we export all of the controls that we define.

forms.index.ts (2)

...
export * from "./controls/controls.index";
(u) We need to add an export statement for the controls index file within our forms index file.

form-examples.module.ts (2)

...
import {
    ..., XcInputComponent
} from "./form-examples.index";
...
@NgModule({
    ...
    declarations: [
        ...,
        XcInputComponent
    ],
    ...
})
export class FormExamplesModule { }
...
(v) Since we want to use the input control in our examples we need to declare the component within our module.

And now we have an input control

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

Time to add a reference to our basic example component (w). If all has gone as planned we should be seeing (x) in our browser.

basic-example.component.pug (2)

xc-form-group(...)
    xc-input(label="Input")
(w) Time to add a reference to our basic example template.
If all went as planned we should see a label and a text box in our browser.
(x) If all went as planned we should see a label and a text box in our browser.
Exciton Interactive LLC
Advertisement