Wróć do strony głównej
Angular

Template Driven Forms i Reactive Forms

Które wybrać?

Formularze reaktywne są bardzo fajne, możemy w sposób dynamiczny tworzyć nowe kontrolki, walidować je synchronicznie lub asynchronicznie na bardzo różne sposoby. To wszystko również da się osiągnąć poprzez formularze template driven, natomiast będą wymagały więcej powtarzalnego kodu.

Z drugiej strony jeśli chcemy dodać jedną kontrolkę np. checkbox, który ukrywa/pokazuje nam pewien komponent, to tworzenie formularza, po czym dodanie listenerów jest przerostem formy nad treścią. Wystarczy ngModel z atrybutem ngModelChange.

Dlatego odpowiadając na pytanie – oba. Szczegóły implementacji TDF i RF zostały bardzo dobrze opisane w dokumentacji jak i na blogu angular.love. W niniejszym artykule skupię się na ich połączeniu.

Template Driven Forms

Jestem angularowcem od pierwszych wersji Angulara (konkretnie od 1.3), więc naturalną dla mnie drogą w chwili przesiadki do Angulara 2 były Template Driven Forms. Dobrze zaimplementowane sprawdzają się świetnie na projektach.

Tutaj muszę rozwinąć co znaczy dobrze zaimplementowane. Wyobraźmy sobie, że chcemy dodać formularz w pliku app.component.html

To nie jest dobra implementacja. Co jeśli chcielibyśmy dodać, kilka podobnych formularzy? Dużo podobnego kodu, który w przyszłości będzie trudny do zmiany.

O wiele lepiej jest zrobić z tego komponent. 

Stwórzmy moduł MyForms i w nim komponent input-text (jest to uproszczenie, w prawdziwym projekcie radziłbym stworzyć moduł biblioteki, a dopiero w nim moduł naszych formularzy).

Od razu możemy dodać ten komponent do exportu (na chwilę obecną mamy tylko jeden, ale w przyszłości będzie ich więcej).

Teraz w app.component próbujemy go użyć (pamiętajcie o imporcie MyModule).

Dodałem również atrybut name, który przyda się na później (jak będzie osadzony w formularzu).

Powinniście zobaczyć teraz tylko napis: input-text works!

Co jest w porządku, bo przecież nie mamy gotowego naszego komponentu.

Zacznijmy od TS

Dodajmy provider oraz niezbędną implementację ControlValueAccessor.

Plus pola label oraz name.

I HTML

Teraz nam elegancko działa i w bardzo łatwy sposób tworzymy nowe pola.

Jeśli będziemy w przyszłości chcieli zmienić wygląd lub rozszerzyć o nowe możliwości, zmieniamy w jednym miejscu i zmieni się wszędzie.

Reactive Forms

Przewagą reactive forms jest możliwość tworzenia dynamicznych formularzy w pliku TS. Jeśli używacie Reactive Forms razem z inputami, czyli tak:

to nie jest to najlepsze podejście. Zdecydowanie warto jest mieć jakiś generator formularzy. Można napisać go samemu, lub skorzystać z genialnej biblioteki ngx-formly.

Instalujemy i dodajemy bibliotekę zgodnie z https://formly.dev/guide/getting-started

Ważne! Potrzebujemy tylko FormlyModule bez FormlyBootstrapModule.

Tworzymy nowy komponent form-input-text (nasza kontrolka, która bazuje na input-text, jednak będzie używana w formularzu) oraz drugi form-field (wrapper dla naszych wszystkich komponentów, obecnie mamy tylko jeden input-text, ale w przyszłości pewnie będzie input-select, input-converter itp. Dzięki wrapperowi nie musimy się przejmować np. stylami jak margines czy wyświetlaniem powiadomień o walidacji).

Całość importujemy do MyForrms jako root.

Rejestrujemy w roocie nasz wrapper oraz naszą kontrolkę. Chcemy ją używać jako “input” oraz chcemy aby był na niej użyty nasz wrapper form-field.

Teraz możemy zająć się kodem inputu oraz wrappera.

Kod HTML Wrappera

Póki co jest bardzo prosty, tylko jedna klasa, oraz wskazane miejsce gdzie mają się wyrenderować kontrolki za pomocą referencji #fieldComponent. W przyszłości dodamy tutaj np. komunikaty o błędach.

Możemy się pozbyć <div class=„form-input”> z input-text.component, klasa będzie w naszym wrapperze.

Plik TS na dany moment tylko rozszerza klasę FieldWrapper.

Podobnie robi plik TS dla FormInputText.

Zaś w jego pliku HTML dodajemy

w przypadku nowszego angulara, trzeba zmienić strictTemplates na false w pliku tsconfig.json.

Mamy wszystko gotowe. Zostało nam jedynie dodać FormlyModule do exportu w MyForms.
Po dodaniu możemy przystąpić do stworzenia naszej pierwszej kontrolki  i w app.component wprowadzamy.

Biblioteka umożliwia naprawdę zrobienie wielu rzeczy. Powyższy przykład jest bardzo prymitywny. Key – jest to klucz pod jakim będzie zapisywana wartość, type to kontrolka która ma się wyświetlać, a w templateOptions przekazujemy label jak i różne atrybutu.

Teraz dodajemy do HTML

To jest reactive forms, więc model jest oczywiście opcjonalny.

Gdzie jest zysk?

Połączenie Template Driven Forms z Reactive Forms z pewnością nie jest dla małych aplikacji. Największy zysk mamy, kiedy większość naszych formularzy jest w Reactive Forms, ale czasami potrzebujemy dodać jakąś kontrolkę. Np. chcemy dodać przełącznik, który odsłoni nam nowy komponent. Przy podejściu tylko Reactive Forms musielibyśmy zdefiniować formularz, potem nasłuchać się na jego zmiany i nie zapomnieć o odsubskrybowaniu.  W przypadku Template Driven wystarczy wkleić ten komponent, ustawić ngModelChange i wszystko będzie działać. 

Co dalej?

Nasz komponent wrapper nie powstał tylko dla HTMLu. Z pewnością można udoskonalić nasz moduł o walidację, aby wyświetlał błąd przy niepoprawnej wartości. To jest natomiast temat na następny post 😉

Link do kodu na github: https://github.com/rograf/angular-forms-sample

O autorze

Rafal Rogulski

Zajmuję się frontendem od angulara 1.3, backend w node.js spodobał mi się wraz z nadejściem NestJS. W wolnych chwilach lubię eksperymentować z nowymi podejściami do pisania kodu. Dobry kod dla mnie musi spełniać trzy cechy: 1. Działać bezbłędnie, 2. Być tak prostym jak się da, 3. Podlegać łatwo zmianom na nowe inne wymagania

Chcesz razem z nami tworzyć treści na bloga? Dołącz do nas i twórz wartościowe treści dla sympatyków Angulara z Angular.love!

3 komentarzy

  1. Jarosław Wróblewski

    „Połączenie Template Driven Forms z Reactive Forms z pewnością nie jest dla małych aplikacji.”
    Angular Material? 🙂

    „Przy podejściu tylko Reactive Forms musielibyśmy zdefiniować formularz, potem nasłuchać się na jego zmiany i nie zapomnieć o zasubskrybowaniu.” Zapinamy się na emisje konkretnej kontrolki przy pomocy .get() i chyba powinni być „i nie zapomnieć o odsubskrybowaniu”? Trzeba bardzo uważać przy pracy z ngrx zeby nie wpaść w pętle emisji

    • Rafal Rogulski

      Ad 1. Mam na myśli kiedy sami chcemy zrobić taką bibliotekę. Potem jest trochę zabawy z wyświetlaniem błędów, walidacją customowych dyrektyw. Dzięki za możliwość doprecyzowania.
      Ad 2. Tak, tutaj oczywiście „odsubskrybowaniu”. Poprawione i dzięki za kolejny wątek, który trzeba brać pod uwagę

Dodaj komentarz

Twój adres e-mail nie zostanie opublikowany. Wymagane pola są oznaczone *