Witam, po niespełna dwóch latach wracamy z kolejną odsłoną Tips & Tricks. Niezmiennie 7 przypadków, startujemy!
1. Debugowanie komponentów w konsoli
Angular w swojej najnowszej 9 wersji wraz z Ivy oferuje nowe api globalnego obiektu ng do debugowania aplikacji w trybie deweloperskim. Pozwala ono m.in. na podejrzenie oraz manipulację stanem naszego komponentu z poziomu konsoli. Dotychczas również mogliśmy korzystać z globalnego obiektu ng, ale nie posiadał on tak szerokiego zestawu metod, jak obecny i nie był zawarty w oficjalnej dokumentacji.
Jedyne co musimy zrobić w celu sprawdzenia stanu naszego komponentu z poziomu konsoli, to:
- otworzenie narzędzi developerskich (np. Chrome Devtools)
- odnalezienie elementu DOM komponentu w zakładce Elements, a następnie zaznaczenie go
- wywołanie następującej komendy w konsoli
- Przed Ivy – ng.probe($0).componentInstance
- Z wykorzystaniem Ivy – ng.getComponent($0)
Należy zwrócić uwagę, że pod $0 znajduje się obecnie wybrany DOM element z zakładki Elements. W przypadku gdy nie będzie to komponent Angularowy nasza komenda zwróci null. Więcej na temat $0, można przeczytać tutaj.
|
Przewagą nowego api nad starym jest np. łatwość, z jaką możemy uruchomić change detection na naszym komponencie w celu odzwierciedlenia zmian wprowadzonych z poziomu konsoli:
- Przed Ivy
123456// Pobranie instancji komponentuconst myComponent = ng.probe($0).componentInstance// Zmiana wartości property namemyComponent.name = ‘Angular love’// Uruchomienie changeDetectionng.probe($0).injector.get(ng.coreTokens.ApplicationRef).tick(); - Z wykorzystaniem Ivy
123456// Pobranie instancji komponentuconst myComponent = ng.getComponent($0)// Zmiana wartości property namemyComponent.name = ‘Angular love’// Uruchomienie changeDetectionng.applyChanges(myComponent)
Wraz z nowym api, pojawiły się też nowe narzędzia t.j SB Angular Inspector, który może oszczędzić Wam wpisywania wspomnianych komend w konsoli.2. Ograniczenie change detection dzięki Event Coalescing
Problem omówimy posługując się prostym przykładem z repozytorium Angulara
1 2 3 |
<div (click)="doSomething()"> <button (click)="doSomethingElse()"></button> </div> |
Po kliknięciu przycisku, ze względu na event bubbling, obie metody zostaną wywołane, a co za tym idzie, change detection również zostanie uruchomione dwukrotnie. Wraz z Angularem v9, mamy możliwość instruowania Angulara, aby łączył zdarzenia, co na podstawie naszego przykładu pozwoli nam ograniczyć uruchomienie change detection do jednego. Robimy to ustawiając nową właściwość ngZoneEventCoalescing na true w metodzie bootstrapModule.
1 2 3 |
platformBrowserDynamic() .bootstrapModule(AppModule, { ngZoneEventCoalescing: true }) .catch(err => console.error(err)); |
Niestety informacji o istnieniu tej flagi na próżno szukać w dokumentacji, mimo iż dodanie jej tam było rekomendowane twórcy tego feature’a 🙂
O tym, jak inaczej z wykorzystaniem zone.js ograniczyć ilość wywoływanych cykli change detection pisaliśmy również w naszym artykule – Angular i zone.js.
3. Użycie const enum zamiast enum
W celu wykazania różnicy pomiędzy tymi deklaracjami enuma musimy zerknąć w kod wynikowy js, dla obydwu podejść
- enum
- const enum
Jak widać, na przykładzie tak prostego enuma, udało nam się ograniczyć kod wynikowy o 5 linijek. Co przy większej skali może pozytywnie wpłynąć na rozmiar naszego bundle’a.
W większości przypadków const enum zaspokoi nasze potrzeby, aczkolwiek jest jeden przypadek, kiedy na pewno nie możemy go użyć. Ma to miejsce w momencie, gdy planujemy używać wartości naszego enuma w sposób dynamiczny np.iterując się po jego kluczach. Wartości kluczy przy użyciu const są rozstrzygane w momencie kompilacji, później działający już program nie ma do nich dostępu, dlatego iteracja po nich jest niemożliwa.
4. Bezpieczny switch dla union type
Wyobraźmy sobie bardzo powszechny problem:
- mamy enum reprezentujący wszystkie wartości mogące trafić do naszego switcha
- mamy switcha, wewnątrz którego chcemy obsłużyć wszystkie klucze enuma
1 2 3 4 5 6 7 8 9 10 11 12 13 |
enum MyOptions { OptionOne, OptionTwo, } function foo(option: MyOptions): void { switch (option) { case MyOptions.OptionOne: return; case MyOptions.OptionTwo: return; } } |
Świetnie, a teraz do naszego enuma dodamy kolejną wartość.
1 2 3 4 5 |
enum MyOptions { OptionOne, OptionTwo, OptionThree } |
Czy obecnie zostaniemy poinformowani o konieczności obsłużenia nowej wartości wewnątrz naszego switcha? Niestety nie.
Aby wymusić pokazanie nam tej informacji, musimy dodać klauzulę default wewnątrz której, przypiszemy wartość wybranej opcji do property zatypowanego jako never.
1 2 3 4 5 6 7 8 9 10 |
function foo(option: MyOptions): void { switch (option) { case MyOptions.OptionOne: return; case MyOptions.OptionTwo: return; default: const exhaustCheck: never = option; } } |
Dzięki temu zabiegowi otrzymujemy następujący komunikat o nieobsłużonym kluczu naszego enuma.
5. Content projection – używanie wielu slotów ng-content z selectem
Podczas tworzenia komponentów Angularowych, często występuje potrzeba transkludowania 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.
Mniej znanym faktem jest to, że nic nie stoi na przeszkodzie, aby mieć wiele takich placholderów i każdy dedykowany innemu fragmentowi HTML. W tym celu musimy użyć ng-content z dodatkowym atrybutem – select. Akceptuje on wartości podobne do metody document.querySelector, więc możemy selektować np. według taga, atrybutu, klasy CSS lub łącząc ze sobą wszystkie selektory jednocześnie.
- Przykład templatki rodzica
1 2 3 4 5 6 7 8 |
<my-child-component> <div headerAttr> Header content </div> <div class="fancy-footer"> Footer Content </div> </my-child-component> |
- Przykład templatki dziecka
1 2 3 4 5 6 7 8 9 |
<div> <ng-content select="[headerAttr]"></ng-content> <p> Main content </p> // select by class <ng-content select=".fancy-footer"></ng-content> </div> |
- Wynikowy HTML dziecka
1 2 3 4 5 6 7 8 9 10 11 |
<div> <div headerattr> Header content </div> <p> Main content </p> <div class="fancy-footer"> Footer Content </div> </div> |
6. Analiza bundle’a aplikacji z wykorzystaniem webpack-bundle-analyzer
Twoja aplikacja ładuje się wolno i szukasz sposobu jak ją przyspieszyć? Weryfikacja bundle’a aplikacji jest dobrym punktem wyjścia podczas próby usprawnienia wydajności. Webpack bundle analyzer za pomocą przejrzystej wizualizacji pozwala zobaczyć ile miejsca zajmują poszczególne paczki w naszym bundle’u oraz zidentyfikować niewykorzystywane bądź zbędne zależności.
7. Unikaj używania metod na template
Trzeba przyznać, że używanie metod na templatce jest wygodnym i szybkim rozwiązaniem, ale niestety może doprowadzić do poważnych problemów z wydajnością naszej aplikacji. Dzieje się tak dlatego, że metoda na templatce będzie wywoływana przy każdym cyklu change detection.
Zalecane są następujące alternatywy w sytuacji gdy wartość którą chcemy wyświetlić jest wyliczana:
- manualne kalkulowanie property wyświetlanej na templatce w momencie, gdy wartości od których ona zależy ulegną zmianie.
- użycie pure pipes, które, zostaną wywołane tylko wtedy kiedy wartość wejściowa lub jej referencja ulegnie zmianie.
- skorzystać z memoizacji. Kiedy wprowadzimy ponownie tę samą wartość wejściową do naszej memoizowanej funkcji, zwraca ona wartość zapisaną w cache zamiast ponownie uruchamiać funkcję, zwiększając w ten sposób wydajność.
Więcej na temat pure pipes oraz memoizacji znajdziecie w artykule – Angular performance tips.
W celu dokładniejszego zrozumienia problemu zachęcam Cię do zapoznania się z jednym z dwóch poniższych artykułów
- https://blog.appverse.io/why-it-is-a-bad-idea-to-use-methods-in-the-html-templates-with-angular-2-30d49f0d3b16
- https://medium.com/showpad-engineering/why-you-should-never-use-function-calls-in-angular-template-expressions-e1a50f9c0496
To by było na tyle dzięki!
PS dajcie znać w komentarzach, które sztuczki z tej serii tips & tricks znaliście, a które okazały się dla was czymś nowym.
Świetny post! super ze blog sie reaktywowal!
ContenProjection wykorzystuje dość często, ale reszta nowość. Ta seria tipów naprawdę jest rewelacyjna, z każdej wynosi się coś nowego.
Uświadamia też jak bardzo nie zna się frameworka:/