Heja! Angular Tips & Tricks będzie nową serią artykułów, w których będę pokazywał drobne rzeczy, porady, dobre praktyki, tricki, które będą dość krótkie, więc same w sobie nie zasługują na osobne artykuły. No to zaczynamy ; )
1. Struktura SharedModule
Zapewne stosowanie SharedModule (moduł dla komponentów współdzielonych w całej aplikacji, które mają wiele instancji, np. modale, taby etc.) i CoreModule (moduł dla komponentów, które mają jedną instancję dla całej aplikacji, np. login / sidebar) jest Ci znane. Jak poprawnie trzymać nasze komponenty w SharedModule? Jeśli trzymasz je jak poniżej:
To możesz to zrobić lepiej:
Różnica jest oczywista. Nie duplikujemy nazw komponentów, moduł jest czytelniejszy i łatwiejszy w utrzymaniu, bo tylko raz deklarujemy nazwę. Już przy około 45 komponentach, sharedModule w wariancie II ma około 40% mniej objętości kodu niż w wersji 1.
2. Rozbudowana dyrektywa ngClass
Dyrektywa ngClass jest bardzo przydatna. O ile opiera się na 1 klasie, np:
1 |
<div [ngClass]="{'tab-active': isTabActive}">Settings</div> |
To jest jak najbardziej w porządku. Ale jak dochodzimy do bardziej rozbudowanych wariantów, np.
1 |
<div [ngClass]="{'active-tab': isTabActive && isTabInView, 'submenu': isSubMenu, 'multi-tab': isMultiTab }">Settings</div> |
To wiedz, że dzieje się źle! Zaśmiecamy templatkę i wynosimy zbyt dużo logiki do widoku. Lepiej zrobić to w następujący sposób:
- w klasie komponentu tworzymy gettera:
1 2 3 4 5 6 7 |
get getCssClassesForTab() : void { return { 'active-tab': this.isTabActive && this.isTabInView, 'submenu': this.isSubMenu, 'multi-tab': this.isMultiTab }; } |
- i na widoku wyłącznie:
1 |
<div [ngClass]="getCssClassesForTab">Settings</div> |
Podobny patent polecam dla rozbudowanych dyrektyw ngStyle.
3. Kolejność deklaracji dyrektyw ma znaczenie
Wyobraźmy sobie przykład, że jedna dyrektywa korzysta z atrybutów, nadanych przez inną dyrektywę na tym samym elemencie, np:
1 |
<p directiveOne directiveTwo>Hello World</p> |
DirectiveTwo nadaje elementowi atrybut X, a dyrektywa DirectiveOne korzysta między innymi z atrybutu X do zrobienia czegoś tam innego. DirectiveOne będzie mieć dostęp do atrybutu X, pod warunkiem, że directiveTwo została wcześniej zadeklarowana w module w tablicy declarations, tzn:
1 |
declarations: [ DirectiveTwo, DirectiveOne ]; |
Kolejność ich wypisania w tagu HTML nie ma znaczenia! Jeśli dyrektywy są zadeklarowane w osobnych modułach (DirectiveOne w ModuleOne, a DirectiveTwo w ModuleTwo), to znaczenie ma kolejność importów modułów:
1 |
imports: [ModuleTwo, ModuleOne]. |
Dobrze to wiedzieć, ponieważ Angular nie zwróci Ci błędu. Twoja dyrektywa po prostu otrzyma undefined na danym atrybucie / wartości pobranym z innej dyrektywy.
4. Atrybut Style i jednostki
Jeśli używasz dyrektywy style wraz z procentami w ten sposób:
1 |
<div [style.width]="someValue + '%'">Hey!</div> |
To lepszym rozwiązaniem jest przeniesienie procentów do dyrektywy, tzn:
1 |
<div [style.width.%]="someValue">Hey!</div> |
Podobnie możesz robić ze wszystkimi jednostkami (np. em, px, rem etc).
5. Wyświetlenie komponentu-dziecka, dopiero jak otrzyma dane
W aplikacjach opartych o komponenty sporo korzystamy z zagnieżdżonych komponentów. Jeśli komponent rodzic przekazuje do komponentu dziecka dane asynchroniczne, to musimy w jakiś sposób powiedzieć Angularowi, aby dopiero załadował komponent dziecko, jak dane dostaną dostarczone (inaczej otrzymamy błąd). Możemy to zrobić w bardzo prosty sposób, z użyciem *ngIf, np:
1 |
<user-list [users]="users" *ngIf="users"></user-list> |
Dzięki prostemu trickowi *ngIf, nasz komponent user-list poczeka z ukazaniem się na widoku, aż komponent-rodzic dostarczy mu listę userów.
6. Rekurencja na widoku
Potrzebujesz wyświetlić dane z pliku JSON, a nie wiesz ile będzie poziomów zagnieżdżenia tego samego typu obiektu? Nic nie stoi na przeszkodzie, aby w templatce komponentu, odnosić się do selektora tworzonego komponentu, tak samo jak w środku funkcji, możesz wołać nazwę tej funkcji w celu uzyskania rekurencji.
Przykładowo masz pliki JSON z drzewami genealogicznymi, każde dziecko ma swoje dzieci, ale dostajemy rodziny z różną ilością pokoleń. Możemy załatwić to następująco:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 |
@Component({ selector: 'children', template: ` <div *ngFor="let child of children"> <ul> <li> {{child.name}} <children [children]="child.children" *ngIf="child.children"></children> </li> </ul> </div> `, }) export class ChildrenComponent { @Input() children; } |
Czyli w templatce komponentu Children wołamy komponent Children.
Plunker do powyższego przykładu:
7. Router navigate i promise
Między innymi w oficjalnym tutorialu Angulara spotykamy się z metodą navigate() klasy Router, która może wyglądać następująco:
1 2 3 |
gotoDetail() { this.router.navigate(['/detail', this.selectedHero.id]); } |
Co ciekawe, metoda navigate zwraca nam Promise<boolean>, dzięki temu po zakończeniu nawigacji, możemy wywołać inną funkcję, umieszczoną w .then(), (np. pokazać tosta):
1 2 3 4 5 |
gotoDetail() { this.router.navigate(['/detail', this.selectedHero.id]).then((data) => { console.log(data, 'promise resolved') }); } |
Argument data w powyższej funkcji, przyjmuje wartość true (promise resolved) lub false (promise rejected).
To by było na tyle dzisiaj! Co jakiś czas będę wrzucać zebrane złote myśli 🙂
Czekam na cz.2 ! GJ
W przypadku podpięcia funkcji do ngClass, Angular wywołuję tą funkcję w interwale co ok 2sekundy dla każdego elementu i odświeża classy w DOM.
Nie fajnie to wygląda przy wielu elementach.
Skąd masz informacje o intervale 2 sekundowym?
W przypadku funkcji, nie jest odpalana w intervale co 2 sekundy, tylko za każdym razem, gdy uruchomi się system detekcji. Jeśli jest możliwość, można użyć strategii onPush, aby temu zapobiec.
Co do odświeżania klas w DOM w przypadku funkcji to się zgodzę. Aczkolwiek Angular jest mocno wydajny, naprawdę trzeba byłoby najebać takich w funkcji w [ngClass], aby wpłynęło to na performance.
Świetny artykuł!
Hej,
Dotarłem tu przypadkiem i strasznie jestem zagubiony.
Czy tu nie ma żadnych tagów czy spisów, aby łatwo przejść przez kolejne części tego cyklu?
Wchodząc z google na jedną ze stron trudno przejść na stronę główną.
musisz kliknać w główny napis u góry z nazwa bloga:) pomyślę, jak to zoptymalizować:)
Dzięki.
Generalnie chciałem napisać, że dużo wartościowych informacji tutaj znalazłem.
Pozdrawiam
super! dzięki za miły feedback i zapraszam częściej
>Już przy około 45 komponentach, sharedModule w wariancie II ma około 40% mniej objętości kodu niż w wersji 1.
Ugh 🙂 IMO koncept share module to jeden z tych nietrafionych w angularze (i nie jestem w tym osamotniony).
Ja majac polowe mniej deklaracji w shared niz Ty, ubijalem to na mniejsze moduly. Biblioteki UIowe jak meterial maja praktycznie 1module-1component. Dla mnie jak shared ma deklaracje ktore moge wydzielić na zewnątrz – dajmy na to app-input i app-select, to robie sobie od razu jakis FormControlsModule, ktory nastepnie po prostu dla wygody (i wstecznej kompatybilnosci) importuje i reexprotuje w shared.
To sie duzo lepiej skaluje i refactoruje, a na dodatek sie przyda przy optymalizacji chunków.
Pozdro
hej Łukasz,
Zgadzam się z Tobą, też w pracy używamy w Shared dedykowanych modułów np dla kontrolek, fileUploadera etc, w przykładzie pokazałem praktykę którą warto stosować, bo często widzę straszny bajzel w Shared, a dobrze w nim posprzątać, chociażby w sposób w jaki pokazałem 🙂 a jak shared rośnie to oczywiście lepiej go powydzielać
Pingback: ANGULAR 2 TIPS & TRICKS cz. II - Angular.love
Metoda
getCssClassesForTab
ma niepoprawnie otypowaną wartość zwracaną, możeRecord
zamiastvoid
?