Zur Demonstation wird das Control für das CSS Framework Twitter Bootstrap v. 4 realisiert.
Dieses lässt sich mittels foglenden Befehl installieren:
npm install --save bootstrap
Die folgende Component soll das vorhandene native HTML Input vom Type number erweitern.
Deshalb werden mittels folgender CSS Class die Spinner ausgeblendet.
.no-spinners {
-moz-appearance:textfield;
}
.no-spinners::-webkit-outer-spin-button,
.no-spinners::-webkit-inner-spin-button {
-webkit-appearance: none;
margin: 0;
}
Für die Buttons „Hochzählen“ und „Runterzählen“ werden nachfolgend Icons eingesetzt.
Diese werden mittels FontAwesome integriert.
Natürlich kann hier auch aus Performance-Gründen einfach ASCII Code genutzt werden.
Andernfalls kann FontAwesome via CDN im globalen style integriert werden.
@import url(https://stackpath.bootstrapcdn.com/font-awesome/4.7.0/css/font-awesome.min.css);
Das Tutorial setzt ein Grundverständnis des Angular FormBuilder Service vorraus.
In der AppComponent wird nun eine FormGroup mittels Angular FormBuilder erstellt.
Der FormGroup wird ein neues Control spinnerValue mit dem Initialwert 0 hinzugefügt.
Das neu zugewiesene Control wird später an das Custom Form Control gebunden.
import { Component } from '@angular/core';
import { FormGroup, FormBuilder } from '@angular/forms';
@Component({
selector: 'my-app',
templateUrl: './app.component.html'
})
export class AppComponent {
myForm: FormGroup;
constructor(private fb: FormBuilder) {
this.myForm = this.fb.group({
spinnerValue: 0
});
}
}
Im folgenden Abschnitt ist das Grundgerüst des Spinner FormControl.
Hierfür wird eine neue Component mit dem Namen spinner.component.ts erstellt.
Vordefiniert ist das Template, welches eine FormGroup beinhaltet indem ein Input und 2 Buttons angzeigt werden.
Die Buttons dienen dem Hoch- und Runterzählen des Input-Values.
Durch Implementieren des Angular Interface ControlValueAccessor müssen folgende Funkionen integriert und ausimplementiert werden:
import { Component, Input, forwardRef } from '@angular/core';
import { ControlValueAccessor } from '@angular/forms';
@Component({
selector: 'spinner',
template: `
<div class="input-group">
<input type="number" class="form-control no-spinners" />
<div class="input-group-append">
<button class="btn btn-default" type="button" (click)="decrement()"><i class="fa fa-minus"></i></button>
<button class="btn btn-default" type="button" (click)="increment()"><i class="fa fa-plus"></i></button>
</div>
</div>
`
})
export class SpinnerComponent implements ControlValueAccessor {
propagateChange: any = () => { };
increment() { }
decrement() { }
writeValue(value) { }
// required by ControlValueAccessor
registerOnChange(fn) { this.propagateChange = fn; }
registerOnTouched() { }
}
Das Zuweisen des Wertes lässt sich leicht über Get Set Properties realisieren, die eine private Property herausreichen oder deren Wert überschreiben.
Die private Property lautet im weiteren Verlauf _model
_model: number = 0;
get value(): number {
return this._model;
}
set value(value: number) {
this._model = value;
this.propagateChange(value); // ChangeDetection von Angular informieren
}
Im Template der Component sind bereits 2 Funktionen den Buttons zugewiesen.
Wenn einer der Buttons geklickt wird, soll der Wert der Component entsprechend abgeändert werden und später mittels formControl oder ngModel gebunden werden.
increment() {
this.value = this.value + 1;
}
decrement() {
this.value = this.value - 1;
}
Damit die Wertänderungen initial auch richtig übergeben werden, muss die Funktion writeValue minimal wie folgt implementiert werden.
changeValue(value: number) {
this.value = value;
}
writeValue(value) {
if (value) {
this.value = value;
}
this.changeValue(this._model); // initiale Wertzuweisung
}
import { Component, Input, forwardRef } from '@angular/core';
import { ControlValueAccessor, NG_VALUE_ACCESSOR } from '@angular/forms';
@Component({
selector: 'spinner',
template: `
<div class="input-group">
<input type="number" class="form-control no-spinners" [(ngModel)]="value" />
<div class="input-group-append">
<button class="btn btn-default" type="button" (click)="decrement()"><i class="fa fa-minus"></i></button>
<button class="btn btn-default" type="button" (click)="increment()"><i class="fa fa-plus"></i></button>
</div>
</div>
`,
providers: [
{
provide: NG_VALUE_ACCESSOR,
useExisting: forwardRef(() => SpinnerComponent),
multi: true
}
],
})
export class SpinnerComponent implements ControlValueAccessor {
propagateChange: any = () => { };
_model: number = 0;
increment() {
this.value = this.value + 1;
}
decrement() {
this.value = this.value - 1;
}
changeValue(value: number) {
this.value = value;
}
writeValue(value) {
if (value) {
this.value = value;
}
this.changeValue(+this._model); // intiale Wertzuweisung
}
// required by ControlValueAccessor
registerOnChange(fn) { this.propagateChange = fn; }
registerOnTouched() { }
get value(): number {
return this._model;
}
set value(value: number) {
this._model = value;
this.propagateChange(value);
}
}
Zuvor muss die Component einem Angular Module – hier app.moduel.ts – integriert und den declarations hinzugefügt werden.
Nun kann die Component im Template integriert werden und dem zu Beginn erstellten FormControl zugewiesen werden.
Hierfür wird in app.component.html die Component wie folgt integriert:
<spinner [formControl]="myForm.get('spinnerValue')"></spinner>
Um zu prüfen, ob die Wertänderungen auch im Formular ankommen, kann dieses schnell ausgegeben werden:
Hierfür nimmt man den value der zu Beginn erstellten FormGroup und nutzt das JSON Pipe von Angular:
<pre>{{myForm.value | json }}</pre>
Dieses Angular Custom Form Control kann nun über weitere Inputs ergänzt werden.
Eine Idee wäre das Integrieren von Wertintervallen, die beim Hoch- oder Runterzählen übersprungen werden sollen.
z.B.: 10er Schritte
Ersteller der Webseite MuchaDev. Selbstständiger IT Constultant für Frontend Technologien.