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:
<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.
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:
http://wp.angular.love/2017/10/03/angular-dyrektywy-strukturalne/
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:
<div>
<ng-template *appHasRole="[Role.ADMIN]" #someContainerForDynamicComponents></ng-template>
</div>
DOBRZE, ZADZIAŁA ;):
<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!