Angular: porządek, modularyzacja, standaryzacja taka sytuacja
Wielokrotnie podkreślam w swoich materiałach, że największą zaletą Angulara jest jego architektura oraz wbudowane rozwiązania, które umożliwiają tworzenie kompleksowych aplikacji, bez przeznaczania sporej liczby godzin, na konfigurację.
Angular powstał z myślą o aplikacjach biznesowych i z tego powodu będę kładł nacisk nie tylko na
aspekty techniczne, ale także na aspekty biznesowe.
Warto abyś od samego początku nauki zwracał uwagę na aspekty biznesowe.
Takie podejście ułatwi oraz znacznie przyspieszy Twój rozwój w obszarze Angulara.
W trym artykule posiłkuję się screenami z kodem. Artykuł ma za zadanie zwrócić także uwagę na aspekt teoretyczny. Zdaję sobie sprawę, że czasami trudno jest przekazać wiedzę techniczną w postaci czytego tekstu, dlatego na samym końcu tego artykułu znajdziesz nagranie wideo. Nagranie to posiada ten sam kod, który jest prezentowany w artykule, natomiast prowadzę tam krok po kroku od stworzenia pierwszej aplikacji, zainstalowania zalezności, konfiguracji serwera mockowego i wiele więcej.
Główne założenia
Przejdźmy do omówienia głównych zalet i założeń jakie prezentuje Angular.
Przede wszystkim w każdym obszarze aplikacji oraz architektury króluje separacja, modularyzacja oraz atomowość.
Mądrze brzmiące słowa, ale co one tak naprawdę oznaczają?
Zacznijmy od podstawowej konstrukcji jaką jest komponent.
Komponent: główna konstrukcja w Angularze
Przedstawmy to na przykładzie:
Dosyć prosty przykład prezentacji samochodów w formie karty. Prezentujemy podstawowe informacje o samochodzie oraz jego zdjęcie. W klasycznym podejściu przy tworzeniu strony, przy pomocy czystego HTML’a oraz CSS’a, aby zaprezentować przypadek ze screena, musielibyśmy przygotować w jednym głównym pliku HTML trzy razy niemal identyczny kod.
Kod różni się między sobą jedynie wartościami jak: marka, model oraz zdjęcie, natomiast cała konstrukcja jest w każdym przypadku taka sama.
Karta posiada główny kontener "card-example", wspólne dla każdej karty "header-img" oraz "card-body".
W takim przypadku gołym okiem widać, że konstrukcja techniczna komponentu jest niezmienna i aż prosi się, aby wydzielić ten fragment kodu, jako reużywalny komponet.
Ważne w programowaniu jest trzymać się zasady DRY (dont’t repeat yourself). Chodzi o fakt, aby nie powielać kodu.
Spójrzmy najpierw z czego składa się komponent po jego utworzeniu.
(Komponent może być utworzony przy wykorzystaniu narzędzia, jakim jest Angular CLI oraz
komendy
ng g c nazwa-komponentu.
Przykład użycia znajdziesz w artykule 5 kroków, aby zacząć w filmie o Angular CLI).
1. Komponent
W wideo poniżej omawiam teorię, bez przykładów praktycznych. Przykłady praktyczne znajdziesz na samym końcu artykułu w wideo: Angular podstawy w 45 minut. Znajdziesz tam kod, wykorzystywany na grafikach w tym artykule.
Komponent: podział na trzy pliki
W Angularze bardzo ważna jest separacja, z tego powodu komponent składa się z trzech oddzielnych plików.
Zostaną utworzone następujące pliki: HTML, SCSS oraz TS.
Plik HTML jest odpowiedzialny za część prezentacji komponentu. Fragment kodu z poprzedniego podejścia, odpowiedzialny za prezentację karty, może zostać tutaj umieszczony.
Plik SCSS to plik odpowiedzialny za dostosowanie styli do danego komponentu.
Pozostaje jeszcze ostatni plik Type Script. Plik ten jest odpowiedzialny za logikę biznesową, przechowywanie i inicjalizowanie zmiennych, pobieranie danych z backendu itd.
Plik HTML: szablon komponentu
Spójrzmy jak mógłby wyglądać szablon komponentu dla naszego przykładu z kartami samochodów:
Porównując ten zapis z zapisem poprzednim zauważysz, że szkielet karty się nie zmienił i pozostał taki sam. Zmieniła się natomiast parametryzacja poprzez wprowadzenie dynamicznych wartości. (Zmienne oznaczone kolorem pomarańczowym).
Plik Type Script: logika biznesowa
Spójrzmy jak wygląda plik Type Script:
1) Dekorator – w tym przypadku jest to dekorator @Component({}). Klasy w Angularze
posiadają różne adnotacje.
Dzięki temu mechanizm Angulara wie jak dane klasy traktować oraz
jakie cechy im nadać.
2) Dekorator @Component posiada w sobie zmienną selector. Selector to nic innego jak tag html pod który należy się odwołać, jeżeli chcemy umieścić komponent karty wewnątrz innego szablonu
3) Zmienne templateUrl oraz stylesUrls to zmienne odpowiedzialne za połączenie komponentu z plikami odpowiedzialnymi za szablon komponentu oraz za stylowanie
4) Zmienne oznaczone adnotacją @Input() – dynamiczne zmienne przekazywanie z komponentu,
w którym komponent card zostanie wykorzystany. Dzięki takiemu podejściu, komponent może być
reużywalny i przyjmować różne wartości.
(W naszym przypadku, różne marki, modele oraz
zdjęcia
samochodów)
Przykład przekazania parametrów do zmiennych @Input() z poziomu szablonu. (plik app.component.html)
2. Serwis
W wideo poniżej omawiam teorię, bez przykładów praktycznych. Przykłady praktyczne znajdziesz na samym końcu artykułu w wideo: Angular podstawy w 45 minut. Znajdziesz tam kod, wykorzystywany na grafikach w tym artykule.
Serwis: główne założenia
Serwis w przeciwieństwie do komponentu składa się z jednego pliku. (Podczas generowania serwisu przy uzyciu komendy: ng g s nazwa-serwisu, zostaną utworzone dwa pliki *.ts oraz *spec.ts. Plik z rozszerzeniem *.spec.ts jest odpowiedzialny za testy)
Serwis w Angularze jest z założenia singletonem (o ile zadeklarujesz go poprawnie w jednym nadrzędnym module). Fakt, że serwis jest singletonem oznacza, że zostanie utworzona tylko jedna instancja tego serwisu, a co za tym idzie będzie to konstrukcja, która będzie przechowywała stan i wartości w trakcie działania aplikacji.
W jakich sytuacjach skorzystać z serwisu?
Serwis: przykład użycia
Zrobimy tutaj najbardziej popularny przykłąd użycia serwisu, czyli pobranie danych z backendu.
Spójrzmy jak wygląda przykładowy serwis do pobierania danych z backendu:
1) Dekorator – w tym przypadku jest to dekorator @Injectable({}). Dekorator @Injectable() posiada w sobie zmienną providedIn: 'root'. Takie domyślne ustawienie będzie gwarantowało, że serwis będzie dodany do głównego modułu w aplikacji, będzie widoczny w całej aplikacji oraz będzie singletonem. (na potrzeby tego wpisu, ten przypadek jest wystarczający)
2) Wstrzykiwanie zalezności - Wstrzyknięcie zależności serwisu HttpClient. Dokładniej opiszę mechanizm wstrzykiwania zalezności w punkcie 4. Wstrzyknięcie serwisu HttpClient pozwala odwoływać się do jego instancji oraz korzystać z metod jak get, która służy do pobrania danych z backendu (dla metody HTTP GET)
3) Zwracany typ Observable - Observable jest ciekawą konstrukcją, z którą wielu początkujących programistów ma problem. Observable to typ tzw. strumieniowy. Grafika poniżej tłumaczy w jaki sposób możesz traktować typy strumieniowe. (Strumienie to analogia do strumienia wody, dostarczanego w rurach)
4) Konfiguracja strzału do endpointa – jak wspominałem wcześniej w tym artykule, serwis jest
odpowiedzialny za przygotowanie konfiguracji związanej z danym endpointem do pobrania danych.
Fizyczne zapytanie i pobranie danych powinno odbywać się w komponencie, przy wykorzystaniu
operatora subscribe.
Ważne do zapamiętania!
Subskrypcji w 99% dokonujemy w komponencie. Powinieneś unikać dokonywania subskrypcji w
serwisie!
Wykorzystanie serwisu w komponencie i pobranie danych
Dane zostaną pobrane i wyświetlone w komponencie/szablonie app.component.
1) Wstrzykiwanie zależności – wstrzyknięcie serwisu cardService. Zabieg taki pozwala na dostęp do instancji tego serwisu, a nastepnie wykorzystanie metody fetchCards().
2) Inicjalizacja danych - interfejs OnInit oraz metoda ngOnInit() są związane z cyklem życia komponentu. Wielu programistów popełnia błąd inicjalizując zmienne w komponencie wewnątrz konstruktora. Jest to błędne podejście. W większości przypadków inicjalizacja danych powinna odbywać się wewnątrz nadpisanej metody ngOnInit.
3) Wykorzystanie metody z serwisu - po wstrzyknięciu instancji serwisu cardService, mamy możliwośc odwołania się do metody fetchCards, która jest odpowiedzialna za pobranie konfiguracji związanej z endpointem do pobrania kart z backendu. (Nie jest to jeszcze fizyczny moment strzału do backendu i pobrania danych)
4) Zapobieganie wyciekom pamięci - w wielu poradnikach spotkałem się z przykładami, gdzie nikt nie
zwraca uwagi na problem związany z wyciekami pamięci. Gdy dokonujemy subskrypcji, należy pamiętać,
aby się także od strumienia odsubskrybować. Operator .pipe oraz metoda takeUntil() dokonuje
odsubskrybowania w momencie, gdy u użytkownik opuści widok z danym komponentem.
Klasa Destroyable, która jest odpowiedzialna za mechanizm odsubskrybowania, udostępnia
subject this.destroyed$.
Ważna uwaga!
Klasa Destroyable nie jest wbudowana w Angulara i należy ją stworzyć samemu. Jest to jedna z
dobrych
praktyk i rekomendowane podejście.
Więcej na temat dobrych praktyk znajdziesz w artykule:
10 dobrych praktyk w pracy z Angularem
Przykład klasy Destroyable
5) Subskrypcja - jest to miejsce, w którym dokona się pobranie danych. Observable jest tzw. lazy więc nie zacznie emitować danych, dopóki nie zostanie dokonana na nim subskrypcja. Dane z backendu są przechowywane w nawiasach ogrągłych przed dunkcją strzałkową oraz przypisane do zmiennej this.cards
6) Publiczna zmienna - jest to zmienna przechowująca karty pobrane z backendu. Zmienna jest publiczna, ponieważ będzie wykorzystana w szablonie HTML tego komponentu. Jeżeli zmienne i metody nie będą wykorzystywane w szablonie (metody nie powinny być wykorzystywane w szablonie. Zamiast metody skorzystaj z pipe'a) to powinny mieć one zasięg widoczności private
szablon app.component.html
6a) Iteracja po tablicy kart - w tym miejscu dokonujemy iteracji (pobrania każdego elementu z tablicy) oraz przekazania parametrów karty do zmiennych @Input() komponetu app-card. Zmienna cards przechowuje w sobie trzy elementy (trzy karty) z tego powodu wynikowy kod będzie zawierał utworzone trzy karty app-card
wygląd aplikacji
3. Moduł
W wideo poniżej omawiam teorię, bez przykładów praktycznych. Przykłady praktyczne znajdziesz na samym końcu artykułu w wideo: Angular podstawy w 45 minut. Znajdziesz tam kod, wykorzystywany na grafikach w tym artykule.
Moduł: główne założenia
Jak już wspominałem, Angular opiera się w głównej mierze na separacji funkcjonalności, obszarów technicznych oraz obszarów techniczno-biznesowych.
Komponent był odpowiedzialny za wydrębianie pojedynczych technicznych/biznesowych obszarów. Dzięki temu komponent staje się reużywalną konstrukcją oraz zamyka pewien mały obszar w odrębny byt. (W naszym przypadku była to karta do prezentowania danych o samochodzie)
Moduł spełnia podobne funkcje co komponent, natomiast na większą skalę. Komponent był odpowiedzialny za mniejsze pojedyncze obszary, natomiast moduł zbiera te wszystkie konstrukcje w jedną całość. Dzięki takiemu podejściu, możemy tworzyć moduły takie jak: moduł menu, moduł widoku głównego. Na przykładzie aplikacji bankowej łatwo wyodrębnić poszczególne moduły. Moduł płatności, moduł ustawień, moduł pożyczek itd.
W naszym przykładzie utworzymy folder dashboard oraz przeniesiemy do niego folder card. folder dashboard będzie folderem nadrzędnym i będzie zawierał w sobie poszczególne karty. Nastepnie dodamy komponent dashboard do którego przeniesiemy kod z komponentu app. Kolejnym krokiem będzie umieszczenie szablonu komponentu dashboard wewnątrz szablonu komponentu app.
Aby takie podejście było możliwe, opakujemy obszar dashboardu w moduł, który będzie przechowywał w sobie wszystkie konstrukcje skłądające się na dashboard. Dodamy do modułu: CardComponent, DashboardComponent, CardService, CommonModule, HttpClientModule oraz wyeksportujemy DashboardComponent, aby można było go użyć wewnątrz szablonu app.component.html.
Mały refactor
1. Zacznij od dodania komponentu dashboardu w folderze app (ng g c dashboard).
2. Nastepnie przenieś (poprzez drag and drop) folder card, do folderu dashboard.
3. Przenieś cały kod zawarty w komponencie app do komponentu dashboard (przenieś kod z plików .ts, .html oraz .scss)
4. Dodaj moduł dashboard (ng g m dashboard) w folderze dashboard.
5. Oczyść app.module, aby wyglądał jak na screenie oraz dostosuj dashboard.module, aby rónież wyglądał jak na screenie. (Zwróć uwagę, że w tym momencie app.module będzie importował DashboardModule)
szablon app.component.html
szablon app.component.html
6. W card.service.ts usuń zmienną providedIn: 'root' z dekoratora @Injectable()
7. W szablonie app.component.html dodaj sam selector app-dashboard
Po takich zmianach wygląd aplikacji nie powinien się zmienić, natomiast opakowaliśmy nasz obszar biznesowy (dashboard do wyświetlania kart samochodów) w jeden niezależny moduł. Podczas rozbudowy aplikacji oraz dodaniu odpowiedniego routingu, będziesz mógł w łatwy sposób sprawić, aby moduł dashboard był lazy loadowany, co znacznie poprawi wydajność aplikacji.
Praktyczny przykład - Angular podstawy w 45 minut
Po zapoznaniu się z teorią, warto sprawdzić jak sprawdza się ona w praktyce. W tym wideo zobaczysz na bardzo prostym przykładzie w jaki sposób możesz korzystać z trzech głównych konstrukcji o których mówiliśmy wyżej.
Przejdziemy od podstaw przez stworzenie nowego projektu, dodanie fakeowego backendu, stowrzenie pierwszego komponentu, dodanie serwisu do pobierania danych oraz na sam koniec opakujemy cały obszar w moduł.
Zapisz się na newsletter. Otrzymuj najnowsze artykuły.