#9 Let's Not Forget the Select
Thursday, September 6, 2018
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.
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
Time to create a select control
-
WebUi
-
Source
-
forms
- controls.mixins.pug
-
forms
-
Source
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" )
...
-
WebUi
-
Source
-
forms
-
controls
-
select
- xc-select.component.pug
- xc-select.component.scss
- xc-select.component.ts
-
select
-
controls
-
forms
-
Source
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
xc-select.component.scss (1)
@import "../controls.mixins.scss";
@include formControlGroup;
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"; }
}
-
WebUi
-
Source
-
forms
-
examples
- form-examples.module.ts
- controls.index.ts
-
examples
-
forms
-
Source
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";
...
form-examples.module.ts (1)
...
import {
..., XcSelectComponent, ...
} from "./form-examples.index";
...
@NgModule({
...,
declarations: [
...,
XcSelectComponent,
...
],
...
})
export class FormExamplesModule { }
platformBrowserDynamic().bootstrapModule(FormExamplesModule);
Update our basic example
-
WebUi
-
Source
-
forms
-
examples
-
basic
- basic-example.component.pug
- basic-example.component.ts
-
basic
-
examples
-
forms
-
Source
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" )
...
basic-example.component.ts (1)
...
export class BasicExampleComponent implements ... {
...
private readonly _snippets = new SnippetsManager([
...
["select", { template: 'xc-select(label="select", name="select" )' }],
...
]);
...
}
A select is no good without options
-
WebUi
-
Source
-
forms
-
controls
-
select
- xc-select.component.ts
-
select
- controls.mixins.pug
-
controls
-
forms
-
Source
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)}}
...
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; }
...
}
Give us the options
-
WebUi
-
Source
-
forms
-
examples
-
basic
- basic-example.component.pug
- basic-example.component.ts
-
basic
-
examples
-
forms
-
Source
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" }
];
}
...
}
basic-example.component.pug (2)
...
div.example-container
div.example-content
...
div.controls-template-component
xc-form-group(... )
...
+controlRow("select")
xc-select(..., [options]="tOptions" )
...
...
We are going to need to deal with options again
-
WebUi
-
Source
-
forms
-
controls
-
select
- xc-select.component.ts
- xc-options-form-control.ts
-
select
-
controls
-
forms
-
Source
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; }
}
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"; }
}
My ocd is kicking in
-
WebUi
-
Source
-
forms
-
examples
-
basic
- basic-example.component.scss
-
basic
-
examples
-
sass
-
1-base
- _forms.scss
-
1-base
-
forms
-
Source
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;
}
...
basic-example.component.scss (1)
...
.control-row {
...
pre {
margin-top: 0;
}
}
...