Formularze aktywne/oparte na modelu

Stosowanie dyrektyw w naszych szablonach, daje siłę szybkiego prototypowania bez zbyt wielu elementów. Z drugiej strony formularze reaktywne pozwalają nam definiować formularz za pomocą kodu i zapewniają nam większą elastyczność i kontrolę nad weryfikacją danych.

Podstawy aktywnych formularzy

Na początek musimy upewnić się, że pracujemy z właściwymi dyrektywami i odpowiednimi klasami. W tym celu importujemy ReactiveFormsModule do modułu aplikacji.

To da nam dostęp do komponentów, dyrektyw i dostawców, takich jak FormBuilder,FormGroup i FormControl

W naszym przypadku, aby utworzyć formularz logowania, patrzymy na coś takiego:

app/login-form.component.ts

import { Component } from '@angular/core';
import { FormGroup, FormControl, FormBuilder } from '@angular/forms';

@Component({
  selector: 'app-root',
  templateUrl: 'app.component.html'
})
export class AppComponent {
  username = new FormControl('')
  password = new FormControl('')

  loginForm: FormGroup = this.builder.group({
    username: this.username,
    password: this.password
  });

  constructor(private builder: FormBuilder) { }

  login() {
    console.log(this.loginForm.value);
    // Attempt Logging in...
  }
}

app/login-form.component.html

<form [formGroup]="loginForm" (ngSubmit)="login()">
  <label for="username">username</label>
  <input type="text" name="username" id="username" [formControl]="username">
  <br>

  <label for="password">password</label>
  <input type="password" name="password" id="password" [formControl]="password">
  <br>

  <button type="submit">log in</button>
</form>

Zobacz przykład [028]

FormControl

Zauważ, że obiektom klasy FormControl nadaliśmy takie same nazwy, jak nazwy pól. Ułatwia to dostęp do danych. Możemy odwołać się do pól przez this, bez konieczności odwoływania się do samego formularza.
Każda instancja FormControl może uzyskać dostęp do swojej grupy nadrzędnej za pomocą jej właściwości .root (np. username.root.controls.password).

Sprawdzanie poprawności formularzy aktywnych

FormControl ma dwie własnosci: wartości początkowej i listy walidatorów. Korzystając z poprzedniego formularza logowania, możemy szybko i łatwo dodać weryfikację.

Angular dostarcza na starcie wiele walidatorów. Mogą być importowane wraz z resztą zależności dla przetwarzania formularzy.

app/login-form.component.ts

import { Component } from '@angular/core';
import { Validators, FormBuilder, FormControl } from '@angular/forms';

@Component({
  // ...
})
export class AppComponent {
  username = new FormControl('', [
    Validators.required,
    Validators.minLength(5)
  ]);

  password = new FormControl('', [Validators.required]);

  loginForm: FormGroup = this.builder.group({
    username: this.username,
    password: this.password
  });

  constructor(private builder: FormBuilder) { }

  login () {
    console.log(this.loginForm.value);
    // Attempt Logging in...
  }
}

app/login-form.component.html

<form [formGroup]="loginForm" (ngSubmit)="login()">

  <div>
    <label for="username">username</label>

    <input
      type="text"
      name="username"
      id="username"
      [formControl]="username">

    <div [hidden]="username.valid || username.untouched">
      <div>
        The following problems have been found with the username:
      </div>

      <div [hidden]="!username.hasError('minlength')">
        Username can not be shorter than 5 characters.
      </div>
      <div [hidden]="!username.hasError('required')">
        Username is required.
      </div>
    </div>
  </div>
  <div >
    <label for="password">password</label>
    <input
      type="password"
      name="password"
      id="password" [formControl]="password">

    <div [hidden]="password.valid || password.untouched">
      <div>
        The following problems have been found with the password:
      </div>

      <div [hidden]="!password.hasError('required')">
        The password is required.
      </div>
    </div>
  </div>

  <button type="submit" [disabled]="!loginForm.valid">Log In</button>
</form>

Zauważ, że dodaliśmy raczej solidną walidację zarówno na polach, jak iw samym formularzu, wykorzystując jedynie wbudowane walidatory i pewną logikę szablonu.

Zobacz przykład [029]

Używamy.validi.untoucheddo określenia, czy musimy pokazywać błędy - podczas gdy pole jest wymagane, nie ma powodu, aby powiedzieć użytkownikowi, że wartość jest błędna, jeśli pole nie zostało jeszcze odwiedzone.

Dla wbudowanego sprawdzania poprawności wywołujemy.hasError()na elemencie formularza, a my przekazujemy ciąg reprezentujący funkcję sprawdzania poprawności, którą zawarliśmy. Komunikat o błędzie wyświetla się tylko wtedy, gdy ten test zwróci wartość true.

Własne walidatory formularzy

Poza wbudowanymi walidatorami, możemy tworzyć własne. Załóżmy, że używamy tego samego formularza logowania, ale teraz chcemy przetestować, czy nasze hasło ma gdzieś w sobie wykrzyknik.

app/login-form.component.ts

function hasExclamationMark(input: FormControl) {
  const hasExclamation = input.value.indexOf('!') >= 0;
  return hasExclamation ? null : { needsExclamation: true };
}

password = new FormControl('', [
  Validators.required,
  hasExclamationMark
]);

Prosta funkcja pobiera instancję FormControl i zwraca wartość null, jeśli wszystko jest w porządku.
Jeśli test się nie powiedzie, zwraca obiekt o arbitralnie nazwanej właściwości.
Nazwa właściwości jest używana do testu .hasError ().

app/login-form.component.ts

<!-- ... -->
<div [hidden]="!password.hasError('needsExclamation')">
  Your password must have an exclamation mark!
</div>
<!-- ... -->

Zobacz przykład [030]

Predefiniowane parametry

Posiadanie niestandardowego weryfikatora do sprawdzania wykrzykników może być pomocne, ale co zrobić, jeśli musisz sprawdzić inną formę interpunkcji?
Ciągłe pisanie tego samego w kółko może być przesadą.

Rozważmy wcześniejszy przykład Validators.minLength(5).
W jaki sposób udało im się uniknąć argumentu kontrolującego długość, jeśli walidator jest tylko funkcją? Naprawdę prosto. To nie jest sztuczka Angulara lub TypeScript - to proste domknięcie JavaScript.

function minLength(minimum) {
  return function(input) {
    return input.value.length >= minimum ? null : { minLength: true };
  };
}

Załóżmy, że masz funkcję, która pobiera minimalny parametr i zwraca inną funkcję. Funkcja zdefiniowana i zwrócona od wewnątrz staje się walidatorem. Odwołanie do domknięcia pozwala zapamiętać wartość minimalną, gdy walidator zostanie wywołany.

Zastosujmy to myślenie z powrotem do interpunkcji.

app/login-form.component.ts

function hasPunctuation(punctuation: string, errorType: string) {
  return function(input: FormControl) {
    return input.value.indexOf(punctuation) >= 0 ?
        null :
        { [errorType]: true };
  };
}

// ...

password = new FormControl('', [
  Validators.required,
  hasPunctuation('&', 'ampersandRequired')
]);

app/login-form.component.html

<!-- ... -->
<div [hidden]="!password.hasError('ampersandRequired')">
  You must have an &amp; in your password.
</div>
<!-- ... -->

Zobacz przykład [031]

Sprawdzanie poprawności danych wejściowych za pomocą innych danych wejściowych

Pamiętaj o tym, co zostało powiedziane wcześniej: input ma dostęp do swojego kontekstu nadrzędnego poprzez .root.
Dlatego złożona walidacja może się zdarzyć poprzez eksplorację formularza, poprzez root.

function duplicatePassword(input: FormControl) {
  if (!input.root || !input.root.controls) {
    return null;
  }

  const exactMatch = input.root.controls.password === input.value;
  return exactMatch ? null : { mismatchedPassword: true };
}

// ...

this.duplicatePassword = new FormControl('', [
  Validators.required,
  duplicatePassword
]);

Zobacz przykład [035]