Hej! Czas wrócić do aktywności z nowymi wpisami – będzie więcej krótkich artykułów z przypadkami, które uznałem za interesujące lub sprytne podczas pracy z angularowymi aplikacjami. Jednym z nich jest dyrektywa do obsługi ról. Dzisiaj pokażę, jak radzę sobie z warunkowym wyświetlaniem elementów na podstawie ról użytkownika. Zaprezentuję rozwiązanie, którego prekursorem był kolega z teamu – Tomasz Borowski z https://angular-in-space.pl, a które stosuję do dzisiaj z wielką przyjemnością.
*hasRoleDirective
Naszym celem jest napisanie dyrektywy strukturalnej, którą będziemy mogli używać w następujący sposób w templatce:
1 2 3 4 5 6 7 |
<div *appHasRole="[Role.MODERATOR];else noModeratorRole"> DISPLAYED FOR MODERATOR </div> <ng-template #noModeratorRole> DISPLAYED IF USER HAS NO MODERATOR ROLE </ng-template> |
Przekazujemy w tablicy określone role – a dyrektywa weryfikuje czy profil zalogowanego użytkownika posiada jedną z nich. Brzmi prosto. A kod…jest jeszcze prostszy! Jestem przekonany, że wyświetlałeś/aś elementy warunkowo już wiele razy przy użyciu dyrektywy *ngIf, prawda? Więc po co wynajdywać koło od nowa? Rozszerzmy ją i dodajmy co trzeba.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 |
import { Directive, Input, TemplateRef, ViewContainerRef } from '@angular/core'; import { NgIf } from '@angular/common'; import { AuthService, Role } from './auth.service'; @Directive({ selector: '[appHasRole]' }) export class HasRoleDirective extends NgIf { @Input() set appHasRole(roles: Role[]) { this.ngIf = this.authService.hasRole(roles); } @Input() set appHasRoleElse(templateElse: TemplateRef<any>) { this.ngIfElse = templateElse; } constructor( private templateRef: TemplateRef<any>, private viewContainerRef: ViewContainerRef, private authService: AuthService, ) { super(viewContainerRef, templateRef); } } |
Sprytnie wykorzystujemy dziedziczenie i nadpisujemy pola klasy – this.ngIf oraz this.ngIfElse. Wstrzykujemy również serwis, który będzie weryfikować czy użytkownik posiada zadaną rolę – może być typowy auth.service, gdzie również jest trzymany profil użytkownika, wraz z jego rolami. Pamiętaj, że nazwa selektora musi być zgodna z nazwami inputów (appHasRole, apphasRoleElse). Wpis nie zawiera opisu działania dyrektyw strukturalnych – jeśli nie są Ci znane, zapraszam do dedykowanego artykułu:
To jest całe rozwiązanie – używaj go śmiało z opcją „else” i tablicą wielu ról, którymi może dysponować użytkownik aplikacji. A może masz tylko profil użytkownika i admina i nie potrzebujesz tablicy parameterów z rolami ? Eksperymentuj! W takiej sytuacji wystarczy bezparametrowa dyrektywa *hasAdminRole lub *isAdmin.
Istnieje również pewien gotchas, np. jeśli renderujesz dynamiczne komponenty za pomocą ComponentFactoryResolver, to nie będziesz w stanie nałożyć dyrektywy strukturalnej jednocześnie z templateRef # (Angular na to nie pozwala). Musisz poczekać, aż na pewno element w DOM będzie dostępny i zawołać tworzenie komponentów w hooku ngAfterViewInit.
NIE ZADZIAŁA:
1 2 3 |
<div> <ng-template *appHasRole="[Role.ADMIN]" #someContainerForDynamicComponents></ng-template> </div> |
DOBRZE, ZADZIAŁA ;):
1 2 3 |
<div *appHasRole="[Role.ADMIN]"> <ng-template #cardsViewContainer></ng-template> </div> |
DEMO:
https://stackblitz.com/edit/angular-apgykt?embed=1&file=src/app/has-role.directive.ts
A kolejny wpis, o wykrywaniu niezamkniętych subskrybcji w RxJS 😉 Stay tuned!
Zrobiłem podobną funkcjonalność ale akurat ściągnąłem kawałek kodu *ngIf i go użyłem 🙂 Nie pomyślałem, że można dziedziczyć po *ngIf. Super sprawa 🙂
martwy blog?
hej, na razie martwy 🙂 praca na etacie i nad wlasnym startupem pochłoneła mnie do reszty, na pewno do końca wakacji nie będę blogować. Zapraszam na nowości od drugiej połowy września!
Chyba serio ostro pracujesz bo mamy już koniec stycznia 2020 a tu dalej nic 😛
Bardzo fajny i ciekawy blog. Szkoda tylko, że nie pojawia się nic nowego od jakiegoś czasu. Oby powodem tego był natłok pracy to wtedy można to wybaczyć. 😉
„A kolejny wpis, o wykrywaniu niezamkniętych subskrybcji w RxJS”
No czekam, czekam 😛
btw, u mnie nie działa ;): „Can’t bind to 'appHasRole’ since it isn’t a known property of 'div’. ”
Nie jest tak, że dyrektywa strukturalna powinna sprawdzać tylko, czy warunek jest spełniony na poziomie jej wywołania?
https://angular.io/guide/structural-directives#write-a-structural-directive
Dobra, problem był z czym innym 😉
Super blog, ostatnio pojawiły się też bardzo ciekawe artykuły i bardzo się z tego cieszę! 🙂