Celem tej prelekcji było pokazanie potencjału formularzy reaktywnych, w szczególności tego, jak radzą sobie ze złożonymi formularzami z dużą ilością walidacji.

Każda kontrolka w formularzu (wybór, wejście, pole wyboru itp.) ma właściwość valueChanges, Observable, który możemy subskrybować, aby obserwować zmiany w czasie i przekształcać wartości za pomocą operatorów RxJS w funkcjonalny sposób. Dlatego formularz jest traktowany jak strumień danych, w którym każde pole zachowuje się jak Observable.

Spójrzmy na przykład formularza reaktywnego:

import { Component, OnInit } from \'@angular/core\';
import { FormControl } from \'@angular/forms\';

@Component({
    selector: \'app-login-form\',
    template: `
      <input type="text" [formControl]="name">
    `
)
export class LoginComponent implements OnInit {
  name = new FormControl(\'name\');
  ngOnInit() {
     this.name.valueChanges.subscribe(data => console.log(data));
  }
}

Jak widać, zadeklarowaliśmy właściwość jako FormControl i możemy ją zainicjować dowolną wartością i połączyć z formularzem wejściowym z szablonu. W ten sposób są zawsze zsynchronizowane. Mogę obserwować zmiany wartości wprowadzone w szablonie poprzez wyraźną subskrypcję właściwości valueChange lub użyć metody setValue, aby zmienić wartość w komponencie i odzwierciedlić ją w szablonie . Działa jak dwukierunkowe wiązanie danych.

W formularzu opartym na szablonie każdy element formularza jest połączony z właściwością Model (wewnątrz logiki komponentu) za pomocą dyrektywy ngModel. Z tego powodu formularze TPL są asynchroniczne. Dyrektywa ngModel jest połączona z „cyklem życia aplikacji do wykrywania zmian”, więc wartość pomiędzy elementem HTML a komponentem jest aktualizowana po „tyczeniu” pętli zdarzeń.

Dzięki bibliotece RxJS możemy wykorzystać metodę fromEvent do Obserwacji i subskrybowania wydarzenia. Używając dekoratora Angular ViewChild i bez użycia formularzy reaktywnych, możemy pobrać odwołanie do elementu DOM (np. getElementById) i skorzystać z operatorów RxJS do manipulowania danymi wewnątrz elementu wejściowego:

Oczywiście trzeba znać trochę RxJS, przynajmniej podstawowe operatory takie jak map, filter, take, debounceTime i odrębnyUntilChanged.

Do tej pory nie korzystaliśmy z podstawowego konstruktora formularzy reaktywnych z FormBuilder, takiego jak FormControl, FormGroup i FormArray. Możemy ich używać z FormBuilderem lub używać ich samodzielnie, jak w poprzednim przykładzie.

Następny przykład jest nieco trudniejszy, ponieważ wprowadzamy FormGroup i FormBuilder, aby skonstruować nasz formularz i zapewnić lepszą skalowalność naszych formularzy:

import { Component, OnInit } from \'@angular/core\';
import { FormControl, FormBuilder, FormGroup } from \'@angular/forms\';
 
@Component({
    selector: \'app-login-form\',
    template: `
    <form [formGroup]="loginForm" (ngSubmit)="submit()">
      <input type="text" [formControl]="username">
 
      <button [disabled]="loginForm.invalid">Login</button>
    </form>
  `
})
export class LoginComponent implements OnInit {
  loginForm: FormGroup;
  username = new FormControl(\'username\');
  constructor(private formBuilder: FormBuilder) {}
  ngOnInit() {
    this.loginForm = this.formBuilder.group({
      username: this.username
    });
    this.loginForm.valueChanges.subscribe(data => console.log(data));
  }
 
  submit() {
    console.log(this.loginForm.value);
  }
}

Angular udostępnia usługę o nazwie FormBuilder, która pozwala pisać mniej kodu. Aby go użyć, musimy wstrzyknąć go do komponentu za pomocą „Wstrzykiwania zależności kątowej”. Teraz możemy używać FormGroup do łatwego tworzenia formularzy. Wewnątrz FormGroup mamy obiekt składający się z par klucz-wartość, gdzie klucz jest nazwą kontrolki, a wartość jest tablicą opcji, takich jak wartość początkowa, wbudowane walidatory i niektóre niestandardowe walidatory.

Niestandardowy weryfikator

Weryfikator niestandardowy to funkcja, która jako dane wejściowe otrzymuje kontrolę nad miejscem jej zastosowania, a wewnątrz treści możemy obsłużyć kod weryfikacyjny, na przykład dopasowanie do wyrażenia regularnego.

Wewnątrz logiki komponentu mamy trzy sposoby uzyskania odniesienia do sterowania:

  1. form.get('firma')
  2. form.controls[‚firma’]
  3. formularz.kontroluje.firmę

gdzie „forma” jest odniesieniem do mojego formularza. Wszystkie są równoważne. Może to być przydatne do kontrolowania naszego elementu HTML w logice komponentu.

Formularz zagnieżdżony

Grupa formularzy zagnieżdżonych to grupa elementów wejściowych, które musimy wspólnie sprawdzić. Pozwala nam stworzyć tablicę obiektów:

Kilka działających przykładów:

import { Component, OnInit } from \'@angular/core\';
import { FormBuilder, FormGroup, Validators } from \'@angular/forms\';
 
@Component({
    selector: \'app-login-form\',
    template: `
    <form [formGroup]="loginForm" (ngSubmit)="submit()">
      <input type="text" formControlName="username">

      <input type="password" formControlName="password">
      <button [disabled]="loginForm.invalid">Login</button>
    </form>
  `
})
export class LoginComponent implements OnInit {
  loginForm: FormGroup;
  constructor(private formBuilder: FormBuilder) {}
  ngOnInit() {
    this.loginForm = this.formBuilder.group({
      username: [\'\', [Validators.required, Validators.minLength(2)]],
      password: [\'\', [Validators.required, Validators.minLength(4)]],
    });
    this.loginForm.valueChanges.subscribe(data => console.log(data));
  }
 
  submit() {
    console.log(this.loginForm.value);
  }
}
import { Component, OnInit } from \'@angular/core\';
import { FormControl, FormBuilder, FormGroup, Validators } from \'@angular/forms\';
 
@Component({
    selector: \'app-login-form\',
    template: `
    <form [formGroup]="form" (ngSubmit)="send()">
      <input type="text" formControlName="username">
      <div formGroupName="carInfo">
       
        <input type="text" formControlName="model">
        <input type="number" formControlName="kw">
      </div>
      <i class="fa fa-check" *ngIf="form.get(\'carInfo\').valid && form.valid"></i>
      <button [disabled]="form.invalid">Login</button>
    </form>
  `
})
export class LoginComponent implements OnInit {
  form: FormGroup;
 
  constructor(private formBuilder: FormBuilder) {}
  ngOnInit() {
    this.form = this.formBuilder.group({
      username: [\'\', Validators.required],
      carInfo: this.formBuilder.group({
        model: [\'\', Validators.required],
        kw: [\'\', Validators.required],
      })
    });
    this.form.valueChanges.subscribe(data => console.log(data));
  }
 
  send() {
    console.log(this.form.value);
  }
}

Możemy także sprawdzić poprawność pola na podstawie wartości innego elementu wejściowego, np. pól formularza dopasowujących hasło.

Każda grupa formularzy może mieć drugi parametr, w którym możemy określić niestandardowy moduł sprawdzania poprawności dla samej grupy formularzy. Ten moduł sprawdzania poprawności to funkcja, dzięki której możemy przekazać referencje do dwóch pól hasła i sprawdzić w nich dopasowanie.

Gdy nasze formularze staną się bardziej złożone, możemy podzielić je na FormGroup lub podzielić nasz interfejs użytkownika na komponenty. Zatem każdą grupę FormGroup można przedstawić za pomocą elementu niestandardowego:

Formularze dynamiczne

Dzięki FormArray jesteśmy w stanie dynamicznie wyświetlać elementy. Jest to odmiana FormGroup, w której dane są serializowane wewnątrz tablicy, a nie w obiekcie jak w FormGroup. Jest to przydatne, gdy nie wiesz, ile kontrolek będzie przechowywanych w formularzach dynamicznych.

Wreszcie możemy wygenerować formularz w czasie wykonywania w oparciu o strukturę JSON, która może pochodzić z wywołania XHR REST. Struktura JSON może zawierać właściwości takie jak etykieta, typ (wejściowy), walidatory itp.

Wewnątrz szablonu użyjemy dyrektywy *ngFor i ngSwitch, aby wybrać typ danych wejściowych, które będziemy renderować w szablonie.

Problem z tym rozwiązaniem polega na tym, że ngSwitch może się rozwijać, więc możemy zdefiniować inne rozwiązanie za pomocą Hash Map dla naszych komponentów, których możemy użyć do tworzenia szablonów formularzy. Dla każdej kontrolki możemy stworzyć komponent. Zatem w kodzie JavaScript mamy cykl odczytania konfiguracji JSON i zbudowania kontrolki formularza w czasie wykonywania, natomiast w przypadku szablonu możemy zastosować *ngFor, a dla każdej iteracji możemy zastosować dyrektywę o nazwie dynamicField, które tworzy dla nas odpowiedni komponent na podstawie konfiguracji kontrolki.

Jak widzimy, temat jest ogromny i nie jesteśmy w stanie omówić wszystkich jego aspektów w jednym artykule, ale mam nadzieję, że rozbudziłem Twoją ciekawość.