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()
this.formArray = new FormArray([...]);
2) Używając klasy FormBuilder
this.myArray = this.formBuilder.array([...]);
W przypadku naszego kodu, skorzystam z FormBuildera:
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:
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.
get positions() : FormArray {
return <FormArray>this.modelForm.get('positions');
}
Oraz metodę do pushowania nowych grup:
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:
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:
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.
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):
<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:
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.