Wróć do strony głównej
Angular

Angular Tips & Tricks cz. VIII

Ostatnio bardzo prężnie działamy, starając się rozwijać angularową społeczność. Niedawno zorganizowaliśmy w Warszawie 2 MeetUpy, o tematyce związanej z NestJSem oraz Angularem. Dodatkowo, w październiku wzięliśmy udział w warsztatach z Michaelem Hladkim (tu możecie przeczytać o naszych wrażeniach po warsztatach), a obecnie jesteśmy w trakcie przygotowań do największego wydarzenia Angularowego w Polsce – konferencji ngPoland.

Jednakże, pomimo wielu prowadzonych działań nie zwalniamy tempa i staramy się prężnie rozwijać naszego bloga. Ci z Was, którzy obserwują nasz profil na Facebooku na pewno zauważyli, że od jakiegoś czasu zaczęliśmy tworzyć krótkie, lecz treściwe tipy z angularowego świata. Aby ułatwić Wam zapoznanie się z nimi, zebraliśmy je wszystkie w jeden artykuł. Ciekawi?! To zaczynamy!

  1. @Input setter vs ngOnChanges
  2. Required Input
  3. ng-deep
  4. ng-content
  5. ngFor – useful local variables
  6. TrackBy
  7. RxJs subscription
  8. Na zakończenie

@Input setter vs ngOnChanges

Dodanie settera do angularowego @Input’a oraz wykorzystanie lifecycle-hook’a ngOnChanges to dwa różne sposoby na wykrycie i obsłużenie zmiany wartości przekazywanych przez inputy do danego komponentu.

Ale które z nich jest lepsze? Otóż… – to zależy.

Rozwiązanie z wykorzystaniem settera sprawia, że wywołany zostanie on (setter) tylko i wyłącznie wtedy, gdy wartość z nim powiązana ulegnie zmianie (lub zostanie ustawiona po raz pierwszy).

Metoda ngOnChanges wywołana zostanie za każdym razem, gdy wartość w którymkolwiek input’cie ulegnie zmianie (jeśli w ramach jednego cyklu change-detection zmianie ulegną wartości kilku inputów, to nadal skutkuje to pojedynczym wywołaniem). 

Setter sprawdza się świetnie, gdy zależy nam jedynie na nasłuchiwaniu zmian na pojedynczej wartości (w oderwaniu od pozostałych input’ów). 

Jeżeli zależy nam na kombinacji wartości z kilku input’ów, wówczas jedynym wyjściem jest ngOnChanges. Zalecamy wówczas skorzystanie z argumentu tej metody (SimpleChanges), który posiada w sobie informacje na temat tego które z wartości uległy zmianie (w tym dla każdej z nich poprzednią i nową wartość).

Odradzamy korzystania z ngOnChanges bez weryfikacji obiektu SimpleChanges (które pola uległy zmianie), a w miarę możliwości zalecamy korzystanie z setterów.

Required Input

Stworzyłeś Angularowy komponent, który potrzebuje przez ciebie zdefiniowanego Inputa żeby działać? Zobacz jak wymusić na konsumencie tego komponentu, aby wraz z Inputem użył dany komponent. 

Załóżmy, że mamy poniższy komponent:

Żeby wymusić użycie tego komponenta wraz z “name”, dodaj “name” do selektora wewnątrz kwadratowych nawiasów. 

To powie kompilatorowi, że oprócz nazwy elementu “hello”, musi również ten element posiadać atrybut “name”. Przy takiej konfiguracji użycie naszego komponentu bez “name”, spowoduje następujący błąd kompilacji: 

ng-deep

Zakładamy, że nie umknęło Wam, że już jakiś czas temu dekorator ten został uznany za deprecated. Na szczęście są alternatywy dla stylowania elementów zagnieżdżonych komponentów.

Możemy w zasadzie skorzystać z dwóch opcji:

  • ustawić stylowanie dla elementów zagnieżdżonego komponentu w pliku z globalnymi stylami,
  • ustawić property encapsulation w metadanych komponentu, który ma stylować zagnieżdżone komponenty, na wartość: ViewEncapsulation.None, a następnie ostylować elementy w pliku ze stylami dla tego komponentu.

Oba te podejścia mają swoje wady i zalety, jednak w obu przypadkach gwarantem bezpieczeństwa, że style nie zostaną zaaplikowane tam, gdzie byśmy nie chcieli, jest dbałość o precyzyjne selektory dla elementów, które chcemy ostylować.

Jakie są Wasze doświadczenia w tym temacie? Napotkaliście na jakieś trudności przy wykorzystaniu którejś z tych opcji, skorzystaliście z innej, a może nadal używacie ng-deep?

ng-content

Podczas tworzenia komponentów Angularowych, często występuje potrzeba projekcji fragmentu HTML z rodzica do dziecka. Oznacza to, że możemy efektywnie ‘wstrzyknąć’ HTML do templatki dziecka. Aby to osiągnąć użyjemy tagu ng-content jako swego rodzaju placeholder na ‘wstrzykiwaną’ wartość wewnątrz templatki dziecka.

Nic nie stoi na przeszkodzie, aby mieć wiele takich placecholderów i każdy z nich dedykowany danemu fragmentowi HTML. W tym celu musimy skorzystać z tagu ng-content z dodatkowym atrybutem – select. Akceptuje on wartości podobne do metody document.querySelector, więc możemy selektować np. według tagu, atrybutu, klasy CSS lub łącząc ze sobą wszystkie selektory jednocześnie.

W przypadku, gdy przekazywany element spełnia warunek więcej niż jednego placeholdera zostanie on wstrzyknięty do pierwszego z nich.

Natomiast w miejsce tagu ng-content bez selektora zostaną wstrzyknięte wszystkie elementy nie spełniające warunków żadnego z pozostałych selektorów.

ngFor – useful local variables

Dyrektywę strukturalną ngFor zna chyba każdy. Korzystamy z niej, gdy potrzebujemy iterowania po kolekcji w templacie. Lecz pewnie nie każdy pamięta o wszystkich przydatnych zmiennych lokalnych, jakie ona dostarcza. Oto one:

  • index: number: indeks obecnego elementu, jak zawsze liczony od 0,
  • count: number: liczba wszystkich elementów,
  • first: boolean: flaga mówiąca o tym, czy element jest pierwszy,
  • last: boolean: flaga mówiąca o tym, czy element jest ostatni,
  • even: boolean:  flaga mówiąca o tym, czy indeks elementu jest parzysty,
  • odd: boolean: flaga mówiąca o tym, czy indeks elementu jest nieparzysty,

Do zmiennych przypisywać możemy aliasy na kilka różnych sposobów (jak pokazano na przykładzie poniżej). Przy bardziej rozbudowanych szablonach, a w szczególności przy zagnieżdżeniu ngFor wewnątrz innego ngFor, warto nadawać im precyzyjne nazwy.

Pamiętajcie też o tym, że zmienne even, i odd mówią o (nie)parzystości indeksu, a nie samego elementu (dla pierwszego elementu, z indeksem równym 0, wartości są następujące: even === true; odd === false).

Dajcie znać, czy wam też zdarzało się ‘ręcznie’ wyliczać niektóre z tych wartości (np. first zastępować wyrażeniem index === 0)?

TrackBy

Czy zdarza Wam się używać funkcji trackBy przy okazji korzystania z tej ngFor?

Jest to dobry sposób na poprawę performance, zwłaszcza, gdy kolekcja po której iterujemy, może się zmienić.

Domyślnie Angular śledzi elementy kolekcji po referencji. Wykonanie operacji na kolekcji spowoduje zmianę referencji, dlatego, w momencie gdy dane się zmieniają np. w wyniku akcji użytkownika czy requestu do API, Angular rozpozna zmiany referencji, usunie z DOM wszystkie elementy kolekcji i utworzy je na nowo. Im większy rozmiar kolekcji, tym większy, negatywny wpływ na performance.

 Z pomocą przychodzi wykorzystanie funkcji trackBy, która umożliwia Angularowi wyłapywanie dodanych lub usuniętych elementów.

Funkcja ta przyjmuje jako argument index oraz aktualny element. Powinna zwracać unikalny identyfikator dla elementu.

Dzięki temu, w przypadku zmiany kolekcji, zamiast wykorzystywać referencje, Angular będzie śledził elementy w oparciu o ustalony, unikalny identyfikator. Wówczas rozpozna on, które elementy zostały dodane lub usunięte i w rezultacie utworzy lub usunie tylko te z nich, które się zmieniły.

RxJs subscription

Jeśli kiedykolwiek zauważyliście, że aplikacja po zniszczeniu komponentu i stworzeniu go na nowo zaczyna zajmować coraz więcej pamięci to prawdopodobnie macie do czynienia z memory leakiem. Często ich powodem są niewyczyszczone subskrypcje. Jednym ze sposobów na usunięcie subskrypcji jest wywołanie metody unsubscribe() na obiekcie Subscription zwracanym przez metodę subscribe(), ale to nie jedyna możliwość. Chętnie dowiemy się jakie są wasze ulubione i znienawidzone metody usuwania subskrypcji i dlaczego?

Poniżej przedstawiamy kilka przykładów:

Wykorzystując UntilDestroy:

Wykorzystując Subject:

Wykorzystując subscription add:

data typing in dialogs

Czy tworząc komponent, którego użyjesz jako materialowego dialogu zdarza Ci się korzystać w szablonie tego komponentu z dyrektywy mat-dialog-close do przekazania danych po jego zamknięciu? Jest to jak najbardziej poprawne podejście, ale w łatwy sposób może stać się ono przyczyną trudnego do odnalezienia buga, którego na szczęście możemy w prosty sposób uniknąć!

Problem leży w tym, że parametr dialogResult który ona przyjmuje jest typu any! W ten sposób możesz niepostrzeżenie zwracać dane niezgodne z oczekiwanym typem.

Lepszą alternatywą jest dostarczenie w konstruktorze obiektu referencji do otwartego dialogu:

    dialogRef: MatDialogRef<ExampleDialogComponent, ExampleDialogOutput>

jego pierwszy typ generyczny jest obligatoryjny, i oczywiście wskazuje typ użytego komponentu, drugi zaś, istotny w naszym przypadku mówi o typie zwracanych danych.

Następnie wykorzystujemy go w metodzie wywołanej z przycisku zamykającego dialog, np.:

    this.dialogRef.close({ result: ‘I love Angular <3’ });

Zgodność typów jest tutaj oczywiście zachowana, bo this.dialogRef.close przyjmuje argument zadeklarowanego typu ExampleDialogOutput.

Co jeszcze jest ważne? Żeby zawsze deklarować interfejsy dla danych doprowadzanych do takiego komponentu i dla danych z niego otrzymywanych i użycie ich właśnie w komponencie dialogu, oraz w komponencie z którego ten dialog jest otwierany!

this.dialog.open<ExampleDialogComponent, ExampleDialogInput, ExampleDialogOutput>(

ExampleDialogComponent,

{ data: inputObj }

).afterClosed()

Zadeklarowanie wprost jakiego typu powinny być dane wejściowe zapewnia nam, że zmienna inputData jest właśnie określonego typu, natomiast zadeklarowanie typu danych wyjściowych sprawia, że wartość uzyskana z Observable’a zwróconego przez metodę afterClosed będzie zatypowana poprawnym typem.

Podsumowując – dzięki tym prostym, dwóm zasadom:

  1. Dostarczaj dialogRef: MatDialogRef ExampleDialogComponent, ExampleDialogOutput> i używaj metody close do zamykania dialogu z przekazywaniem danych zamiast dyrektywy mat-dialog-close,
  2. Deklaruj interfejsy dla danych wejściowych i wyjściowych i używaj ich zarówno w komponencie dialogu, jak i w miejscu jego otwierania,

jesteśmy w stanie zapewnić całkowitą spójność typów danych przekazywanych do oraz z komponentu dialogu!

Na zakończenie

Jak sami widzieliście, trochę angularowych tipów udało nam się do tej pory uzbierać. Jednak nie zwalniamy tempa! Możecie być pewni, że na bieżąco będziemy Wam przekazywać wszystkie angularowe wskazówki i nowinki. Jeśli nie chcecie niczego przegapić, zachęcamy do obserwowania naszych profili na Facebooku oraz na Twitterze. Właśnie tam najczęściej informujemy Was o wydarzeniach i Meetupach angularowych organizowanych przez House of Angular czy ngPoland, kodach rabatowych na kursy i szkolenia, konkursach, w których macie okazję wygrać nasze angularowe gadżety, bilety na wydarzenia, szkolenia czy kursy. Oprócz tego oczywiście zawsze dajemy tam znać o nowych artykułach, ciekawostkach i nowinkach ze świata Angulara i NestJSa! 🙂

O autorze

Mateusz Dobrowolski

Sympatyk Typescripta mający kilkuletnie doświadczenie w tworzeniu Angularowych aplikacji.

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!

0 komentarzy

Dodaj komentarz

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