Benutzerdefinierte Steuerelemente im Winkel

Prolog


Lassen Sie uns über reaktive Formen im Winkel sprechen und für benutzerdefinierte Steuerelemente lernen, wie diese erstellt, verwendet und validiert werden. In diesem Artikel wird davon ausgegangen, dass Sie mit dem Winkelrahmen bereits vertraut sind, sich jedoch eingehender mit dessen Besonderheiten befassen möchten. Guten Wunsch, fangen wir an.

Reaktive und Template-gesteuerte Formen


Ein paar Worte, um sicherzugehen, dass wir uns auf derselben Seite befinden. Ein Winkel hat zwei Arten von Formen: Vorlagengesteuerte Formen und reaktive Formen .

Vorlagengesteuerte Formulare sind bidirektionale, bindungsbasierte Formulare (hi, angularjs). Wir geben das Feld in der Klasse an (z. B. Benutzername), im HTML-Code am Eingabetag, an das wir [(Wert)] = "Benutzername" binden, und wenn sich der Eingabewert ändert, ändert sich der Wert des Benutzernamens. 2011 war das verdammt magisch! OK, aber es gibt eine Nuance. Auf diese Weise wird es schwierig, komplexe Formen zu erstellen.

Reactive Forms ist ein praktisches Werkzeug zum Erstellen einfacher und komplexer Formulare. Sie sagen uns: "Erstellen Sie eine Instanz der Formularklasse ( FormGroup ), übergeben Sie ihr Instanzen der Steuerelemente ( FormControl ) und geben Sie sie einfach an HTML weiter. Den Rest erledige ich." Ok, lass es uns versuchen:

class UsefullComponent { public control = new FormControl(''); public formG = new FormGroup({username: control}); } 

 <form [formGroup]="formG"> <input type="text" formControlName="username"> </form> 

Als Ergebnis erhalten wir eine reaktive Form mit Blackjack und ... Sie verstehen, mit allen Annehmlichkeiten. Mit formG.valueChanges erhalten wir beispielsweise einen Stream mit beobachtbaren Formularänderungen. Sie können auch neue Steuerelemente hinzufügen, vorhandene löschen, Validierungsregeln ändern, den Formularwert ( formG.value ) abrufen und vieles mehr. Und das alles mit einer Instanz von formG .

Um nicht jedes Mal manuell Instanzen der oben genannten Klassen zu erstellen, haben uns Angle- Entwickler einen praktischen FormBuilder zur Verfügung gestellt, mit dessen Hilfe das obige Beispiel folgendermaßen umgeschrieben werden kann:

 class UsefullComponent { public formG: FormGroup; constructor(private fb: FormBuilder) { this.formG = fb.group({ name: '' //  new FormControl() !! }) } } 

Benutzerdefinierte Steuerelemente


Angular sagt uns: "Bro, wenn Sie nicht genug native Steuerelemente (Eingabe, Datum, Nummer und andere) haben oder wenn Sie sie für etwas überhaupt nicht mögen, ist hier ein einfaches, aber sehr leistungsfähiges Tool zum Erstellen Ihrer eigenen." Benutzerdefinierte Steuerelemente können auch mit reaktiven, auch mit vorlagengesteuerten Formularen verwendet werden und werden mithilfe von Direktiven implementiert (überrascht!). Da wir wissen, dass die Komponenten Direktiven mit einer Vorlage sind (was Sie brauchen), schreiben wir:

 import { Component, Input } from '@angular/core'; @Component({ selector: 'counter-control', template: ` <button (click)="down()">Down</button> {{ value }} <button (click)="up()">Up</button> ` }) class CounterControlComponent { @Input() value = 0; up() { this.value++; } down() { this.value - ; } } 

Wie jede Direktive muss dies im Modul deklariert werden (damit der Artikel nicht wächst, lassen wir solche Nuancen weg).

Jetzt können wir die erstellte Komponente verwenden:

 import { Component } from '@angular/core'; @Component({ selector: 'parent-component', template: ` <counter-control></counter-control> ` }) class ParentComponent {} 

Es funktioniert natürlich alles, aber wo sind die Formen? Zumindest vorlagenbasiert ....
Keine Panik, alles wird nach dem Treffen mit ControlValueAccessor sein .

Kontrollwert-Accessor


Damit Winkelmechanismen mit Ihrem benutzerdefinierten Steuerelement interagieren können, benötigen Sie dieses Steuerelement, um eine bestimmte Schnittstelle zu implementieren. Diese Schnittstelle heißt ControlValueAccessor . Dies ist ein ziemlich gutes Beispiel für Polymorphismus aus OOP, wenn unser Objekt (in diesem Fall Control) eine Schnittstelle implementiert und andere Objekte (Winkelformen) über diese Schnittstelle mit unserem Objekt interagieren.

Dank ControlValueAccessor haben wir eine einzige Möglichkeit, mit Steuerelementen zu arbeiten, die übrigens nicht nur zum Erstellen benutzerdefinierter Steuerelemente verwendet wird. Angular under the Hood nutzt ebenfalls diese Schnittstelle. Wofür? Und nur um das Verhalten nativer Steuerelemente zu vereinheitlichen. Bei der Eingabe ist der Wert beispielsweise im Attribut value enthalten. In der Checkbox wird der Wert über das Attribut checked bestimmt. Daher verfügt jeder Steuerungstyp über einen eigenen ControlValueAccessor: DefaultValueAccessor für Eingaben und Textbereiche, CheckboxControlValueAccessor für Kontrollkästchen, RadioControlValueAccessor für Optionsfelder usw.

Aber was macht Angular mit ControlValueAccessor ? Alles ist ganz einfach, es schreibt den Wert aus dem Modell in das DOM (Ansicht) und löst das Änderungssteuerungsereignis auch in FormGroup und anderen Direktiven aus.

Nachdem wir ControlValueAccessor kennengelernt haben , können wir es auf unser Steuerelement anwenden.

Schauen wir uns die Benutzeroberfläche an:

 interface ControlValueAccessor { writeValue(value: any): void registerOnChange(fn: any): void registerOnTouched(fn: any): void } 

writeValue (value: any) - Wird aufgerufen, wenn die Quelle ( new FormControl ('I am default value' ) oder ein neuer Wert über control.setValue ('I am setted value') festgelegt wird .

registerOnChange (fn: any) - Eine Methode, die den Handler definiert, der aufgerufen werden soll, wenn sich der Wert ändert (fn ist ein Rückruf, der das Formular benachrichtigt, dass sich der Wert in diesem Steuerelement geändert hat).

registerOnTouched (fn: any) - definiert den Rückruf, der beim Unschärfeereignis aufgerufen wird (das Steuerelement ist mit touched markiert)

 import { Component, Input } from '@angular/core'; import { ControlValueAccessor } from '@angular/forms'; @Component({ selector: 'counter-control', template: ` <button (click)="down()">Down</button> {{ value }} <button (click)="up()">Up</button> ` }) class CounterControlComponent implements ControlValueAccessor { @Input() value = 0; onChange(_: any) {} up() { this.value++; } down() { this.value - ; } writeValue(value: any) { this.value = value; } registerOnChange(fn) { this.onChange = fn; } registerOnTouched() {} } 


Damit das übergeordnete Formular Änderungen im Steuerelement erkennt , müssen wir die onChange- Methode für jede Änderung des Werts von value aufrufen. Um nicht in jeder Methode (auf und ab) einen onChange- Aufruf zu schreiben, implementieren wir das Wertefeld durch getters und setters:

 // … class CounterControlComponent implements ControlValueAccessor { private _value; get value() { return this._value; } @Input() set value(val) { this._value = val; this.onChange(this._value); } onChange(_: any) {} up() { this.value++; } down() { this.value - ; } // … } 

Derzeit weiß der Angular nicht, dass die Komponente, die ControlValueAccessor implementiert, als benutzerdefiniertes Steuerelement betrachtet werden soll. Sagen wir ihm Folgendes:

 import { Component, Input, forwardRef } from '@angular/core'; import { ControlValueAccessor, NG_VALUE_ACCESSOR } from '@angular/forms'; @Component({ … providers: [{ provide: NG_VALUE_ACCESSOR, useExisting: forwardRef(() => CounterControlComponent), multi: true }] }) class CounterControlComponent implements ControlValueAccessor { … } 

In diesem Codeabschnitt sagen wir zu dem Winkel "Ich möchte ein neues benutzerdefiniertes Steuerelement registrieren ( angeben: NG_VALUE_ACCESSOR ), verwenden die vorhandene Instanz der Komponente (in diesem Moment wird unsere Komponente initialisiert) ( useExisting: forwardRef (() => CounterControlComponent )". .

multi: true gibt an, dass es mit einem solchen Token mehrere Abhängigkeiten geben kann ( NG_VALUE_ACCESSOR ).

Nicht nur geschaffen


Es ist Zeit, unsere benutzerdefinierte Steuerung zu zayuzat. Vergessen Sie nicht, FormsModule / ReactiveFormsModule zu den Importen des Moduls hinzuzufügen, in dem dieses Steuerelement verwendet wird.

In vorlagengesteuerten Formularen verwenden


Hier ist alles ganz einfach: Mit der bidirektionalen Bindung über ngModel erhalten wir eine Ansichtsänderung, wenn sich das Modell ändert, und umgekehrt:

 import { Component } from '@angular/core'; @Component({ selector: 'parent-component', template: ` <counter-control [(ngModel)]="controlValue"></counter-control> ` }) class ParentComponent { controlValue = 10; } 

Verwendung in reaktiven Formen


Wie am Anfang des Artikels angegeben, erstellen Sie eine Instanz des reaktiven Formulars über FormBuilder , rendern Sie in HTML und genießen Sie:

 import { Component, OnInit } from '@angular/core'; import { FormBuilder } from '@angular/forms'; @Component({ selector: 'parent-component', template: ` <form [formGroup]="form"> <counter-control formControlName="counter"></counter-control> </form> ` }) class ParentComponent implements OnInit { form: FormGroup; constructor(private fb: FormBuilder) {} ngOnInit() { this.form = this.fb.group({ counter: 5 }); } } 

Dies ist eine vollwertige reaktive Form mit unseren benutzerdefinierten Steuerelementen, während alle Mechanismen genauso funktionieren wie mit den nativen Steuerelementen (habe ich schon Polymorphismus erwähnt?). Um den aktuellen Artikel nicht zu laden, werden wir im nächsten Artikel über das Überprüfen und Anzeigen von benutzerdefinierten Steuerungsfehlern sprechen.

Material:

Artikel über Custom Controls von Pascal Precht.
Von den Docks bildet sich im Winkel.
Eine Artikelserie über rxjs.

Source: https://habr.com/ru/post/de481424/


All Articles