MODEL DRIVEN FORMS – dynamiczne formularze
W poprzednich artykułach (cz I i cz II) operowałem na formularzach, które składy się ze stałej liczby grup (FormGroup) i kontrolek (FormControl). W tym artykule pokażę, jak dodawać / usuwać / iterować po grupach w formularzu, czyli po prostu tworzyć dynamiczne formularze. Pomoże nam w tym klasa FormArray.
Przykład który rozpatrzymy, jest popularny w kreatorach CV. Użytkownik dodaje kolejne doświadczenie, które z reguły składa się ze stanowiska, zakresu dat, nazwy firmy i miasta. Oczywiście ma również możliwość usunięcia danego doświadczenia. W naszym przypadku, będzie to wyglądać tak:
Każde kliknięcie „Add position”, doda nam kolejną pozycję z doświadczeniem.
Przed przerobieniem tego artykułu, polecam zapoznać się z dwiema poprzednimi częściami, inaczej część treści będzie dla Ciebie niezrozumiała.
Link do plunkera:
ARRAY FORM
Jak wspomniałem w poprzednich artach, pojedynczy FormGroup składa się z dowolnej ilości FormControl, oraz może posiadać zagnieżdżone FormGroup. Natomiast FormArray, może wyglądać następująco:
Każdy FormControl lub FormGroup znajduje się pod kolejnym indeksem, tak jak to jest w standardowych tablicach. Co ważne, grupy i kontrolki mają swoje nazwy, natomiast w FormArray do konkretnej grupy dostajemy się wyłącznie po indeksie.
FORM ARRAY W KLASIE KOMPONENTU
FormArray możemy stworzyć na dwa sposoby:
1) Standardowo poprzez new FormArray()
1 |
this.formArray = new FormArray([...]); |
2) Używając klasy FormBuilder
1 |
this.myArray = this.formBuilder.array([...]); |
W przypadku naszego kodu, skorzystam z FormBuildera:
1 2 3 4 5 |
this.modelForm = this.formBuilder.group({ firstname: ['', Validators.required], lastname: ['',[Validators.required, Validators.minLength(3)]], positions: this.formBuilder.array([]) }); |
Teraz przygotujemy metodę, która będzie nam zwracać pożądany FormGroup:
1 2 3 4 5 6 7 |
buildPosition() : FormGroup { return this.formBuilder.group({ job: '', company: '', city: '' }); } |
Przygotujemy również gettera, którego wykorzystamy na widoku oraz w klasie. Zwróć uwagę, że dodaję cast operator <FormArray>, bez tego zwrócony typ to AbstractControl.
1 2 3 |
get positions() : FormArray { return <FormArray>this.modelForm.get('positions'); } |
Oraz metodę do pushowania nowych grup:
1 2 3 |
addPosition() : void { this.positions.push(this.buildPosition()); } |
Powyższy .push, nie jest „pushem” wbudowanym w JS. Jest to metoda klasy FormArray, i wygląda następująco:
1 |
push(control : AbstractControl) : void |
Z racji, że ustawiliśmy gettera (get) na metodzie positions(), metodę PUSH wywołujemy bezpośrednio na this.positions.
Potrzebujemy jeszcze metody do usuwania grup:
1 2 3 |
removePosition(i) : void { this.positions.removeAt(i); } |
RemoveAt(i), jest również metodą udostępnioną przez klasę FormArray i pozwala nam usunąć grupę na przekazanym indeksie.
1 |
removeAt(i : number) : void |
Podsumujmy, co mamy:
- strukturę formularzu zbudowaną poprzez formBuildera
- metodę, która zwraca nową grupę z pozycją zawodową
- metodę, która pushuje do Positions: FormArray([…]) nową grupę
- oraz kolejną metodę, która pozwoli nam usunąć grupę na dowolnym indeksie
Wszystko co potrzebujemy po stronie kodu – mamy.
FORM ARRAY W WIDOKU KOMPONENTU
Czas na HTML (nie jest to HTML całego formularza):
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 |
<button (click)="addPosition()">Add position</button> <div formArrayName="positions" *ngFor="let pos of positions.controls; let i = index"> <h3>Position {{ i + 1}}</h3> <button (click)="removePosition(i)">remove (X)</button> <fieldset [formGroupName]="i"> <label attr.for="{{ 'posId' + i }}">Job:</label> <input id="{{ 'posId' + i }}" formControlName="job"> <label>Company:</label> <input name="company" formControlName="company"> <label>City:</label> <input name="city" formControlName="city"> </fieldset> </div> |
Co wykorzystaliśmy na widoku?
- dyrektywa formArrayName – tutaj przekazujemy nazwę zadeklarowaną w formBuilderze, gdzie tworzymy główną strukturę formularza
- dodanie dyrektywy *ngFor, która iteruje po positions.controls – pole controls zawiera tablicę z FormGroupami/FormControlsami, przypisaliśmy również index iteracji do zmiennej i
- dyrektywa [formGroupName], do której przekazujemy jako parametr aktualny index. Zwróć uwagę, że korzystamy z data-bound, czyli formGroupName jest kwadratowych nawiasach
- formControlName, które jest Ci znane z poprzednich artów
- metoda removePosition(i) przyjmująca jako parametr index grupy
- metoda addPosition(), która dodaje nową grupę na widoku
Poniżej link do podsumowującego Plunkera:
Co jeszcze min. udostępnia nam klasa formArray:
- length – odczytanie długości tablicy
- setControl – zamiana dowolnej grupy / kontrolki na inną
- insert – dodanie grupy / kontrolki na dowolnym indeksie
- setValue – nadpisanie całej zawartości tablicy
- patchValue – nadpisanie dowolnego elementu w tablicy
Dokładny opis metod i przyjmowanych parametrów, znajduje się w dokumentacji klasy:
Jeszcze trik na koniec: jeśli na starcie chcesz posiadać jedną grupę w FormArray, możesz to zrobić w następujący sposób:
1 |
positions: this.formBuilder.array([ this.buildPosition() ]) |
PODSUMOWANIE
Krótko i na temat Model Driven Forms: bardzo proste i skuteczne narzędzie, z pewnością się przyda w wielu sytuacjach. Możesz również pomyśleć o walidacji – np. zablokować dodanie kolejnej grupy, póki poprzednia nie została odpowiednio wypełniona.
Świetny artykuł! Dzięki!
super, cieszę się, że się spodobał!