10 dobrych praktyk w pracy z Angularem

Po omówieniu najczęściej popełnianych błędów, warto spojrzeć na przeciwną stronę i zainteresować się tematem dobrych praktyk. Zdaję sobie sprawę, że nie będziesz w stanie od razu wykorzystać całego arsenału i wdrożyć go w wytwarzany kod. Najważniejsze na początku jest natomiast, abyś zetknął się z pewnymi koncepcjami oraz, aby pozostał ślad w Twojej głowie.

Gdy będziesz nabierał pewności w pracy z Angularem, z czasem zaczniesz zwracać większą uwagę na jakość kodu i podejście "aby działało" zejdzie odrobinę na bok, aż w końcu ustąpi miejsca podejściu "rozwiązanie działa i jest eleganckie".

Sprawdź 10 dobrych praktyk w pracy z Angularem, aby podnieść jakość swojego kodu.

1. Dodawanie dolarka na końcu zmiennej strumieniowej "nazwaZmiennej$"

Bardzo ważna konwencja nazewnicza. Polega ona na tym, aby dodawać dolarka „$” na końcu nazwy do zmiennych strumieniowych takich jak Observable oraz Subject.

Przykład kodu z biblioteki

Konwencja ta zwiększa czytelność kodu. Gdy przeglądasz kod i na końcu zmiennej widzisz dolara to wiesz, że jest to zmienna strumieniowa. Od razu kojarzysz, że możesz skorzystać z dodatkowych funkcjonalności, jakie strumienie dostarczają oraz wiesz jak z takiej zmiennej korzystać.

2. Odsubskrybowanie ze strumieni

Subskrypcja w komponencie

Jedną z pierwszych dobrych praktyk w Angularze, jest pamiętanie, aby odsubskrybować się od zmiennych strumieniowych. Zapobiega to wyciekom pamięci. Działa to w taki sposób, że odsubskrybowanie dokonywane jest najczęściej podczas niszczenia komponentu. Odpowiedzialny jest za to interfejs OnDestroy oraz nadpisanie metody ngOnDestroy, gdy dokonujemy subskrypcji w komponencie.

Przykład kodu z biblioteki

Aby mieć dostęp do zmiennej this.destroyable$ komponent musi rozszerzyć klasę Destroyable.
(Należy pamiętać, że klasa Destroyable, to customowe rozwiązanie, które możesz dodawać do swoich projektów.)

Przykład kodu z biblioteki

Klasa Destroyable i nadpisana metoda ngOnDestroy

Przykład kodu z biblioteki

Subskrypcja w szablonie

Gdy dokonujemy subskrypcji w szablonie, najpierw powinniśmy przypisać wartość strumienia do zmiennej.

Przykład kodu z biblioteki

Następnie powinniśmy dokonać subskrybcji na tej zmiennej w szablonie poprzez korzystanie z wbudowanego pipe’a async.

Przykład kodu z biblioteki

Wbudowany pipe async samodzielnie zadba o to, aby dokonać odsubskrybowania.

Jak utworzyć klasę Destroyable oraz jak poprawnie odsubskrybować się ze strumieni znajdziesz w poniższym wideo. Odsubskrybowanie poprzez Destroyable oraz pipe async omawiam w 31:33 - 38:17

3. Korzystanie z "early return"

Praktyka ta polega jak sama nazwa wskazuje na wczesnym zakończeniu wykonywania np. funkcji.

Chodzi tutaj o to, żeby sprawdzić najpierw warunek, który z definicji powinien zakończyć wywołanie funkcji i nie pozwolić przejść dalej, ponieważ byłoby to bezsensowne.

W tym przypadku sprawdzamy najpierw czy istnieje kolor w postaci hex. Może się zdarzyć, że do funkcji zostanie przekazana wartość null albo undefined. Jeżeli tak się stanie, to po prostu zwracamy pusty return, który wychodzi z funkcji.

Przykład kodu z biblioteki

4. Korzystanie z "Elvis operator"

Operator ten pozwala na sprawdzenie czy obiekt poprzedzający znak zapytania istnieje.

Przykład kodu z biblioteki

Gdyby zastosować klasyczne podejście kod wyglądałby w następujący sposób:

Przykład kodu z biblioteki

Musielibyśmy najpierw sprawdzić czy istnieje obiekt car i dopiero po takim sprawdzeniu odwołać się do zmiennej photoSource. Tzw. "Elvis operator", pozwala skrócić ten zapis ponieważ zapis car?.photoSource sprawdza samoistnie, czy w tym przypadku, obiekt car istnieje.

5. Łapanie błędów poprzez catchError

Bardzo ważne w aplikacji, aby dodawać obsługę błędów i aby świadomie nią zarządzać.

Angular udostępnia dwa sposoby na złapanie błędów:

1. Łapanie błędów w ciele subscribe:

Przykład kodu z biblioteki

2. Łapanie błędu przed subscribe przy wykorzystaniu operatora pipe:

Przykład kodu z biblioteki

Z moich wieloletnich doświadczeń oraz wielogodzinnych dyskusji w zespołach doszliśmy do wniosku, że lepszą praktyką jest korzystać z operatora catchError przy wykorzystaniu operatora pipe.

Dlaczego takie rozwiązanie wydaje się bardziej eleganckie i jest preferowane?

Przede wszystkim ciało subskrypcji, powinno być tzw. happy path’em, czyli ścieżką gdy wszystko wykonało się poprawnie. W subscribe oczekujemy rezultatu/odpowiedzi.

Z tego powodu, powinniśmy zadbać o to, aby wszelkie błędy złapać zanim przejdziemy do happy path, czyli do pobrania odpowiedzi.

6. Dodanie stanu komponentu

Z reguły, gdy działamy z naszym komponentem posiada on różne stany. Najczęstszym przypadkiem jest pobranie danych do komponentu, np. listy samochodów albo danych do wykresów.

Pobranie danych dzieje się asynchronicznie oraz niejednokrotnie może trwać jakiś określony czas. Gdy zapytania są małe i dostajemy odpowiedź niemal natychmiastowo problem nie jest widoczny na pierwszy rzut oka.

Natomiast, gdy zapytanie trwa np. sekundę lub dwie, może się okazać, że potrzebujemy zarządzić stanem komponentu.

Najczęstsze stany jakie możesz spotkać to SUCCESS czyli, gdy udało się poprawnie pobrać dane, ERROR, gdy coś poszło nie tak i dane nie mogą być pobrane z jakiegoś powodu oraz LOADING podczas ładowania danych (które trwa np. dwie sekundy)

Stan danego kontenera na samym początku powinien być ustawiony jako LOADING.

Przykład kodu z biblioteki

Widok kontenera dla stanu LOADING

Przykład kodu z biblioteki

Kolejne ustawienie stanu na LOADING odbywa się podczas każdej zmiany filtrów i próby pobrania danych z backendu.

Przykład kodu z biblioteki

Gdy wszystko pójdzie dobrze i przejdziemy do "happy path", czyli pobierzemy dane w ciele subscribe powinniśmy ustawić status na SUCCESS.

Przykład kodu z biblioteki

Widok kontenera dla stanu SUCCESS

Przykład kodu z biblioteki

Widok kontenera dla stanu ERROR

Przykład kodu z biblioteki

Dzięki dodaniu stanów możemy w łatwy i klarowny sposób zarządzać wyglądem kontenera i odpowiednio reagować na różne zdarzenia i stany.

Domyślnie taki mechanizm nie jest dostarczony w świeżym projekcie Angulara, natomiast stworzyłem go dla Ciebie w bibliotece. Komponent za to odpowiedzialny to app-view-state.component.ts

W zależności od wartości zmiennej viewState wyświetli się odpowiedni wygląd kontenera (ładowanie = LOADING, pobranie danych = SUCCESS, błąd = ERROR)

Przykład kodu z biblioteki

7. Poprawne wykorzystanie enumów w szablonie

Bardzo często zdarza się, że zdefiniowaliśmy sobie w aplikacji enumy i chcielibyśmy je wykorzystać w szablonie HTML.

Aby tego dokonać, musimy najpierw zaimportować takie enumy i przypisać je do zmiennej w komponencie tego szablonu:

Przykład kodu z biblioteki

Robimy to poprzez dodanie operatora widoczności public następnie nadajemy nazwę zmiennej. W tym przypadku jest to zmienna ViewState (tak samo nazywa się enum), następnie dodajemy dwukropek oraz po dwukropku podajemy słówko kluczowe „typeof” i podajemy typ naszego enuma.

W naszym przypadku jest to typ ViewState, następnie dodajemy znak zapytania w celu przypisania wartości do zmiennej oraz przypisujemy wartość całego enuma czyli właśnie ViewState.

Wykorzystana jest tutaj także konwencja nazewnicza, aby nazwa tej zmiennej była napisana z wielkiej litery. Dzięki temu gdy wykorzystamy tę zmienną w szablonie to od razu widać, że mowa o enumie.

Przykład kodu z biblioteki

8. Korzystanie z enuma podczas tworzenia formularzy

Praktycznie w 99% poradników, podczas tworzenia formularza napotkasz się na następujące podejscie:

Klucze będą tworzone poprzez wpisanie ich jawnie, natomiast dobrą praktyką, jest utworzenie enuma, który przechowuje klucze formularza, a następnie reużywanie ich w wielu miejscach.

Klucze utworzone ręcznie:

Przykład kodu z biblioteki

Klucze utworzone przy pomocy enumów. Przykład w kodzie => add-car-form.service.ts linia 17-21

Przykład kodu z biblioteki

Wykorzystanie enuma w szablonie. Przykład w kodzie => add-car-dialog-form.component.html linia 11

Przykład kodu z biblioteki

9. Funkcje exportowane zamiast klas Utils

W wielu językach (szczególnie w Javie), aby wyodrębnić wspólne funkcje, które chcemy wykorzystać w wielu miejscach w kodzie, utworzymy tzw. klasę utils.

Będzie to klasa, która będzie posiadała funkcje statyczne:

W Angularze dobrą praktyką w takiej sytuacji jest skorzystanie z funkcji eksportowanych.
Przykład w kodzie => form.helper.ts

Przykład kodu z biblioteki

10. Korzystanie z ng-container, przy warunkach

Często spotykałem/spotykam się z podejściem, że sprawdzenie czy lista obiektów istnieją zostaje dodane na tagu div lub section. Problem z takim podejściem jest taki, że gdy w naszym przypadku samochody będą istniały to w wynikowym kodzie DOM, zostanie dodany nadmiarowy pusty div.

Przykład kodu z biblioteki

Mógłbyś zadać pytanie, dlaczego nie można dodać w tym przypadku ngIf’a na tagu app-car-card. Dzieję się tak ponieważ, na tym tagu została już wykorzystana jedna dyrektywa strukturalna jaką jest ngFor, a na jednym tagu można wykorzystać tylko jedną dyrektywę.

Lepszym rozwiązaniem w takiej sytuacji, jest skorzystanie z tagu ng-container. Przewagą tej dobrej praktyki jest fakt, że gdy samochodu będą istniały, nie zostanie dodany pusty tag div, ponieważ ng-container nie odkłada się w wynikowym DOM’ie.

Przykład kodu z biblioteki

Proponowane artykuły:

5 najczęstszych błędów
Poprzedni artykuł
10 pomysłów na aplikację
Kolejny artykuł

Zapisz się na newsletter. Otrzymuj najnowsze artykuły.