In größeren Projekten werden wir relativ schnell den Punkt erreichen in dem sich der ein oder andere Entwickler denkt: „Moment das hatten wir doch schon mal“. Wenn noch nicht geschehen ist das der Zeitpunkt um für den so identifizierten Bereich / Funktion eine eigene Komponente aus zu bilden. Wenn es sich dabei um ein Form-Element handelt mag es zuerst einmal nicht so offensichtlich sein wie man das am besten bewerkstelligt…
Nehmen wir ein einfaches konkretes Beispiel: in unserer Anwendung haben wir eine Auswahlbox von Benutzern durch die z.B. eine Aufgabe, ein Projekt etc. zugeordnet werden soll. Diese Auswahl von Benutzern wird nun an verschiedenen Stellen der Anwendung benötigt. Also entwickeln wir eine eigenen Komponente, was dank Angular-CLI ja auch ein Kinderspiel ist:
ng generate component UserSelector
So einfach ist es in diesem Beispiel jedoch erst einmal nicht. Das Ziel soll es ja sein unsere Komponente als Teil eines Formulars zu verwenden und eine Deklaration analog zu den Standard-HTML Elementen zu erreichen:
<form [formGroup]="form">
<label for="user">Benutzer: </label>
<app-user-selector formControlName="userId" id="user"></app-user-selector>
<label for="comment">Kommentar: </label>
<input type="text" id="comment" formControlName="comment"/>
</form>
app.component.html > Verwendung unserer Komponente
wir wollen hier einen ReactiveForm-Ansatz verfolgen bei dem die Registrierung von Datenmodel und Validatoren in der Komponentenklasse geschieht, dazu wird hier der Name des Controls mittels ‚formControlName‘ eingesetzt. Die Alternative wäre der Einsatz von „ngModel“
In unserem Projekt setzen wir PrimeNG als Komponentenbibliothek ein, unsere UserSelector Komponente wird also lediglich eine Art Wrapper, hier das Template:
<span [formGroup]="form">
<p-dropdown
[options]="users"
optionLabel="name"
class="form-control"
[autoDisplayFirst]="false"
dataKey="id"
formControlName="value"
></p-dropdown>
</span>
user-selector-component.html > Template unserer Komponente
wir verwenden hier eine PrimeNG Komponente ‚p-dropdown‘. Für unsere eigene Komponente spannend ist hier die Erzeugung einer eigenen FormGroup innerhalb unserer Komponente, das ermöglicht uns die PrimeNG Komponente innerhalb „ganz normal“ mit unserer Komponenten-Klasse zu verbinden
import { Component, OnInit, forwardRef } from '@angular/core';
import { FormGroup, ControlValueAccessor, NG_VALUE_ACCESSOR, FormBuilder } from '@angular/forms';
import { DemoService } from '../demo.service';
// 1
@Component({
selector: 'app-user-selector',
templateUrl: './user-selector.component.html',
styleUrls: ['./user-selector.component.css'],
providers: [
{
provide: NG_VALUE_ACCESSOR,
useExisting: forwardRef(() => UserSelectorComponent),
multi: true
}
]
})
//2
export class UserSelectorComponent implements ControlValueAccessor {
users: any[];
form: FormGroup;
//3
constructor(private service: DemoService, builder: FormBuilder) {
service.getAll().subscribe(r => (this.users = r));
this.form = builder.group({
value: ['']
});
//4
this.form.controls.value.valueChanges.subscribe(c => {
this.onChange(c.id);
});
}
//5
writeValue(obj: number): void {
if (obj) {
const value = this.users.find(e => e.id == obj);
if (value) this.form.patchValue({ value });
}
}
//6
registerOnChange(fn: any): void {
this.onChange = fn;
}
registerOnTouched(fn: any): void {
this.onTouched = fn;
}
onChange: any = () => {};
onTouched: any = () => {};
}
user-selector-component.ts > Komponentenklasse
1. die Metadaten für unsere Komponente. Neu an dieser Stelle ist die Registrierung unserer Komponente als Value-Accessor über das Provider Array, sehr einfach ausgedrückt ist dieses Deklaration bei jeder dieser Komponenten technisch bedingt genau so zu deklarieren ( sehr spannende Angular-Hintergrund-Themen zu dieser Deklaration: ‚Multi Providers‘ und ‚forwardRef‘ )
2. wir Implementieren das Interface ControlValueAccessor
3. Konstruktor. Neben einem Demo-Service ( der uns die Daten für unsere Liste ermittelt), lassen wir uns auch den FormBuilder von Anuglar injizieren um die FormGroup auf zu bauen
4. hier wird es spannender: wir registrieren einen Change-Listener an unserer internen FormControl ( die PrimeNG Dropdown-Liste ). Wenn der User hier eine Änderungen vornimmt wollen wir dieses Event „weiter geben“ und den Verwender unserer Komponente informieren, dazu wird die Methode onChange ( Interface ) verwendet. ( In unserem Beispiel nehmen wir hier auch eine Umwandlung vor: unserer DropDown-Liste liefert ein komplexes User-Objekt, in unserer Anwendung, also in den anderen fachlichen Objekten verwenden wir lediglich die Id.
5. writeValue ( Interface ) ist die Methode welche aufgerufen wir um ein Objekt welches aus dem Model kommt in der UI darzustellen, also beim Rendern oder bei Änderungen der Form. Wir bekommen an dieser Stelle die Id des bereits selektierten Benutzers, anhand dessen wir das komplexe User-Objekt aus unserem Array ermitteln und unser internes Formular damit aktualisieren.
6. Interface Methoden um die Registrierungen von EventListenern zu ermöglichen
Das war’s damit haben wir eine Komponente welche ganz einfach in unseren Formularen eingesetzt werden kann. Auf diese Weise lassen sich natürlich auch gänzlich eigene Komponenten ( ohne PrimeNG ) implementieren oder wesentlich komplexere wiederverwendbare Komponenten.