Kurs Angular · Moduł 8: Formularze

Walidacja - funkcja z jednym werdyktem

3 min czytania ·
W tej lekcji3

Strażnik u bramy dojo ma proste zadanie: spojrzeć na przybysza i wydać werdykt - wpuścić albo nie. Walidator w Angularze to dokładnie taka funkcja: patrzy na wartość pola i zwraca jeden z dwóch wyników - null, gdy wszystko gra, albo obiekt z opisem błędu, gdy coś jest nie tak. Zapamiętaj ten kontrakt (null = OK), bo powtórzy się w każdej z trzech części tej lekcji.

Walidatory wbudowane

Najczęstsze reguły Angular ma już gotowe - dopinasz je do pola listą. Nie piszesz logiki, tylko wybierasz strażników z półki.

import { Validators } from '@angular/forms';

this.fb.group({
  name: ['', Validators.required],
  email: ['', [Validators.required, Validators.email]],
  password: ['', [Validators.required, Validators.minLength(8)]],
  age: [0, [Validators.min(18), Validators.max(120)]],
  phone: ['', Validators.pattern(/^\d{9}$/)],
  acceptTerms: [false, Validators.requiredTrue]
});

Zwróć uwagę na wzorzec: gdy reguła jest jedna, podajesz ją wprost (Validators.required); gdy kilka, dajesz je w tablicy - wszystkie muszą przejść. requiredTrue to częsta pułapka do zapamiętania: zwykłe required uzna zaznaczony i odznaczony checkbox za wypełniony, a requiredTrue wymaga właśnie zaznaczenia - idealne do zgody na regulamin.

Walidatory własne

Gdy potrzebujesz reguły spoza półki, piszesz strażnika sam. To funkcja zgodna z tym samym kontraktem: dostaje pole, zwraca null lub obiekt błędu.

import { AbstractControl, ValidationErrors, ValidatorFn } from '@angular/forms';

function forbiddenNameValidator(forbidden: string[]): ValidatorFn {
  return (control: AbstractControl): ValidationErrors | null => {
    if (forbidden.includes(control.value?.toLowerCase())) {
      return { forbiddenName: { value: control.value } };  // źle -> obiekt błędu
    }
    return null;  // OK
  };
}

// Użycie
this.fb.group({
  name: ['', [Validators.required, forbiddenNameValidator(['admin', 'root'])]]
});

Przyjrzyj się kształtowi: funkcja zewnętrzna przyjmuje konfigurację (listę zakazanych nazw) i zwraca właściwy walidator - dzięki temu ten sam strażnik obsłuży różne listy. Wewnętrzna funkcja realizuje kontrakt: pasuje do zakazanych - zwraca obiekt { forbiddenName: ... }, w przeciwnym razie null. Klucz obiektu (forbiddenName) to nazwa błędu, po której rozpoznasz go potem w szablonie.

Walidator asynchroniczny

Niektórych rzeczy nie sprawdzisz na miejscu - np. czy nazwa jest zajęta, wie tylko serwer. Wtedy walidator zwraca nie werdykt od razu, lecz strumień, który werdykt przyniesie.

import { AsyncValidatorFn } from '@angular/forms';
import { map, catchError, debounceTime, switchMap, first } from 'rxjs/operators';
import { of } from 'rxjs';

function uniqueNameValidator(service: SamuraiService): AsyncValidatorFn {
  return (control: AbstractControl) => {
    return of(control.value).pipe(
      debounceTime(300),                       // nie pytaj po każdej literze
      switchMap(name => service.checkNameExists(name).pipe(
        map(exists => exists ? { nameTaken: true } : null),
        catchError(() => of(null))             // błąd sieci -> nie blokuj
      )),
      first()
    );
  };
}

// Async validators to TRZECI argument, osobno od synchronicznych
this.fb.group({
  name: ['', [Validators.required], [uniqueNameValidator(this.service)]]
});

Kontrakt jest ten sam ({ nameTaken: true } albo null), tylko opakowany w strumień, bo odpowiedź przychodzi z opóźnieniem. debounceTime oszczędza serwer, pytając dopiero, gdy użytkownik przestanie pisać, a catchError decyduje, że awaria sieci nie ma blokować formularza. Zapamiętaj jedno miejsce, które łatwo pomylić: walidatory asynchroniczne podajesz jako trzeci argument, oddzielnie od zwykłych - Angular czeka na nie inaczej.

Pamiętaj z tej lekcji jeden kontrakt: walidator to funkcja, która zwraca null przy poprawnej wartości albo obiekt błędu przy złej. Wbudowane bierzesz z półki, własne piszesz tą samą regułą, a asynchroniczne oddają werdykt strumieniem - reszta to szczegóły.

Widzisz błąd w tej lekcji?

Sprawdź się

Odpowiedz na pytania z tej lekcji. Wybierz odpowiedź, a od razu zobaczysz, czy jest poprawna.

  1. 1. Co powinien zwrócić custom validator gdy wartość jest poprawna?

  2. 2. Co powinien zwrócić custom validator gdy wartość jest NIEpoprawna?

To 2 z 3 pytań do tej lekcji. Pozostałe rozwiążesz w grze.

Zadania praktyczne w grze

  • Edytor kodu

    Uzupełnij własny walidator sprawdzający, czy nazwa samuraja nie zawiera cyfr.

  • Układanie w poziomie

    Ułóż tokeny zwracanej funkcji walidatora (jedna linia): (control: AbstractControl) => ValidationErrors | null

  • Układanie w pionie

    Uporządkuj operatory RxJS w łańcuchu walidatora asynchronicznego sprawdzającego unikalność nazwy (kolejność w .pipe(), linia po linii).

Przydatne artykuły