16 maj 2019
5 min

Real Life Case – Dyrektywa do obsługi ról

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!

Podziel się artykułem

Zapisz się na nasz newsletter

Dołącz do community Angular.love i bądź na bieżąco z trendami.