08 lut 2017
5 min

ANGULAR 2 MODEL DRIVEN FORMS, cz. II

MODEL DRIVEN FORMS – zmiana zasad walidacji w locie

Jeśli zapoznałeś się już z podstawami Model Driven Forms, to czas na tzw. mięsko! W tym artykule zaprezentuję, jak podczas runtime’u zmieniać zasady walidacji (conditional validation).

Rozpatrzymy przypadek formularza, w którym:

  • użytkownik podaje obowiązkowo adres email, oraz jeśli chce, to również numer telefonu
  • użytkownik może wybrać, czy chce otrzymywać powiadomienia na maila (domyślnie), czy na telefon
  • jeśli wybierze, że na telefon, to pole z numerem staje się obowiązkowe (required).
  • jeśli wybierze płatność kartą, wszystkie pola dotyczące karty stają się obowiązkowe

Zanim przejdziemy do dalszej części artykułu, polecam spojrzeć jak wygląda stan od którego zaczynamy:

LIVE EXAMPLE

KLASA ABSTRACT CONTROL

Jeśli zapoznałeś się już z kodem, spójrzmy raz jeszcze, jak wygląda struktura naszego formularzu w kodzie:

this.modelForm = this.formBuilder.group({
 firstname: ['', Validators.required],
 lastname: ['',[Validators.required, Validators.minLength(3)]],
 email: ['', Validators.required],
 phone: '',
 subscription: 'email'
});

Naszym celem jest dynamiczne dodawanie i usuwanie Validators.required do pola „phone”, jeśli użytkownik zaznaczy lub odznaczy radio button „sms”.

Rozpoczniemy od napisania metody, którą przypniemy na (click) przy każdym radio buttonie:

setSubscription(notifyBy : string) : void {
 ...
}

Oraz w HTML:

<label>Email</label>
<input type="radio" (click)="setSubscription('email')" value="email" formControlName="subscription">
 
<label>SMS</label>
<input type="radio" (click)="setSubscription('sms')" value="sms" formControlName="subscription">

Jak słusznie zauważyłeś (bądź nie), formControlName może być identyczny dla wielu inputów. Przejdźmy do implementacji metody setSubscription, która obsłuży nam dynamiczną walidację:

setSubscription(notifyBy : string) : void {
  const control = this.modelForm.get('phone');
  if (notifyBy === "sms") {
    control.setValidators(Validators.required);
  } else {
    control.clearValidators();
  }

  control.updateValueAndValidity();
}

Rozkładając powyższy kod na czynniki, znajome już Ci FormGroup oraz FormControl dziedziczą po klasie AbstractControl, która udostępnia nam min. następujące metody:

  • setValidators(newValidator) – jako parametr przyjmuje walidator, który chcemy ustawić. Jeśli nasz FormControl posiada już walidatory dodane w formBuilder.group(), zostaną one nadpisane.W przypadku dodania paru walidatorów, jako parametr przekazujemy tablicę z walidatorami. np:
control.setValidators([Validators.required, Validators.minLength(3)])
  • setAsyncValidators(newValidator) – j/w, z tym, że dla asnychronicznych walidatorów
  • clearValidators() – czyści listę synchronicznych walidatorów
  • clearAnsyncValidators() – czyści listę asynchronicznych walidatorów
  • updateValueAndValidity({onlySelf? : boolean, emitEvent? : boolean}) – wykonuje re-kalkulację wartości i stanu walidacji FormControla / FormGroupa. Domyślnie update wartości i walidacji dotyczy również ancestorów (czyli FormGroupa, który jest nad FormControlem, lub wszystkich FormGroup’ów w górę, jeśli mamy zagnieżdżone FormGroup). Metoda przyjmuje opcjonalny parametr, przekazać możemy obiekt z dwoma opcjonalnymi właściwościami, w przypadku przekazania onlySelf jako true, refresh walidacji i wartości, będzie dotyczyć tylko danej kontrolki.

Brzmi klarownie. Jednak nie musimy się ograniczać do pojedynczego FormControl.

WALIDACJA W LOCIE GRUPY KONTROLEK

Możemy również dodawać walidację w locie na całe grupy kontrolek. Wyobraźmy sobie przykład, że użytkownik może wybrać płatność kartą, gdzie musi podać kod CVV, datę ważności karty, oraz numer karty. W takim przypadku nie ma sensu ustawiać walidatora na każdy FormControl osobno, tylko musimy zebrać to w grupę, co możemy zrobić w następujący sposób:

<fieldset formGroupName="card">
  <label>CVV:</label>
  <input name="cvv" formControlName="cvv">
 
  <label>Card number:</label>
  <input name="cardNumber" formControlName="cardNumber">
 
  <label>Expiration date:</label>
  <input name="expirationDate" formControlName="expirationDate">
</fieldset>

W templatce korzystamy z dyrektywy formGroupName, która wymaga obecności dyrektywy FormGroup. Wrzucamy do środka kontrolki, które mają zostać podpięte pod grupę. Następnie w formBuilderze:

 this.modelForm = this.formBuilder.group({
   firstname: ['', Validators.required],
   lastname: ['',[Validators.required, Validators.minLength(3)]],
   email: ['', Validators.required],
   phone: '',
   subscription: 'email',
   card: this.formBuilder.group({
     cvv: '',
     cardNumber: '',
     expirationDate: ''
   })
 });

Szybko i prosto – wołamy jeszcze raz formBuilder.group(). Na tym etapie, wartość modelu formularza wygląda następująco:

Model Driven Forms -wartości formularza w obieckie

Zagnieżdżony FormGroup, został wrzucony jako obiekt do głównego obiektu z wartościami całego formularza.

Na potrzeby artykułu, walidację całej grupy dodamy po prostu na kliknięcie guzika.

<button (click)="setCardValidation()">Add card?</button>

I nasza metoda setCardValidation():

setCardValidation() : void {
  const cardGroup = this.modelForm.controls['card'];
  const cardGroupControlsKeys = Object.keys(cardGroup.controls);
 
  cardGroupControlsKeys.forEach((key) => {
    cardGroup.controls[key].setValidators(Validators.required);
    cardGroup.controls[key].updateValueAndValidity();
  });
}

Każdy formGroup posiada property controls, które zawiera wszystkie zagnieżdzone kontrolki (formControls).

W powyższym kodzie przeiterowaliśmy po każdym property obiektu card (czyli po każdej kontrolce grupy card), i ustawiliśmy walidację na required.

Dzięki takiemu trickowi, możesz dowolnej grupie przestawić wszystkie pola dynamicznie na required, zamiast np. wołać X razy setValidators na każdym polu, dublując kod.

Plunker podsumowujący artykuł:

LIVE EXAMPLE

PODSUMOWANIE

Wykorzystanie takich metod jak setValidators oraz clearValidators, jest bardzo przydatne w przypadku formularzy, gdzie użytkownik posiada wiele scenariuszy wypełniania formularza (np. użytkownik wybiera, że chce fakturę na firmę – automatycznie dodajemy walidację do pól związanych z firmą, NIP etc.). Więc warto pamiętać, że Angular nam to umożliwia.

Podziel się artykułem

Zapisz się na nasz newsletter

Dołącz do community Angular.love i bądź na bieżąco z trendami.