#2 Creating the Form Group and Our First Control
Friday, June 15, 2018
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.
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
Cleanup on aisle 8
-
WebUi
-
Source
-
forms
-
examples
-
basic
- basic-example.component.scss
- form-examples.component.scss
-
basic
-
examples
-
forms
-
Source
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
}
basic-example.component.scss (1)
h1 { // Remove this
color: red;
}
Defining a form group
-
WebUi
-
Source
-
forms
- xc-form-group.component.pug
- xc-form-group.component.ts
-
forms
-
Source
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
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({});
}
}
All the cool kids have core namespace
-
WebUi
-
Source
-
forms
- forms.core.ts
- forms.index.ts
-
forms
-
Source
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";
forms.index.ts (1)
export * from "./forms.core";
Add the form group to our module
-
WebUi
-
Source
-
forms
-
examples
- form-examples.index.ts
- form-examples.module.ts
-
examples
-
forms
-
Source
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";
form-examples.module.ts (1)
...
import {
XcFormGroupComponent
} from "./form-examples.index";
...
@NgModule({
bootstrap: ...,
declarations: [
...,
XcFormGroupComponent
],
exports: ...,
imports: ...
})
export class FormExamplesModule { }
...
Make sure that an input is set
-
WebUi
-
Source
-
forms
-
examples
-
basic
- basic-example.component.pug
-
basic
- xc-form-group.component.ts
- forms.core.ts
-
examples
-
forms
-
Source
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);
}
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'");
}
}
basic-examples.component.pug (1)
xc-form-group(name="Basic Form Group")
A form is no good without controls
-
WebUi
-
Source
-
forms
-
controls
- xc-control.ts
- xc-form-control.ts
- forms.core.ts
-
controls
-
forms
-
Source
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; }
}
xc-form-control.ts (1)
import { XcControl } from "../forms.core";
export abstract class XcFormControl extends XcControl {
}
forms.core.ts (3)
...
export * from "./controls/xc-control";
export * from "./controls/xc-form-control";
...
Our first form control
-
WebUi
-
Source
-
forms
-
controls
-
input
- xc-input.component.pug
- xc-input.component.scss
- xc-input.component.ts
-
input
-
controls
-
forms
-
Source
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")
xc-input.component.scss (1)
.xc-form-control-group {
}
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;
}
}
Everything can be solved with another index file
-
WebUi
-
Source
-
forms
-
controls
- controls.index.ts
-
examples
- form-examples.module.ts
- forms.index.ts
-
controls
-
forms
-
Source
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";
forms.index.ts (2)
...
export * from "./controls/controls.index";
form-examples.module.ts (2)
...
import {
..., XcInputComponent
} from "./form-examples.index";
...
@NgModule({
...
declarations: [
...,
XcInputComponent
],
...
})
export class FormExamplesModule { }
...
And now we have an input control
-
WebUi
-
Source
-
forms
-
examples
-
basic
- basic-example.component.pug
-
basic
-
examples
-
forms
-
Source
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")