Недавно появилась задача в одном достаточно крупном проекте ограничивать UI и функционал пользователей, в зависимости от их ролей. К этому моменту приложение уже разрослось на 100+ компонент, и это при том что основные базовые компоненты вынесены в отдельную репу и ставятся пакетом. То есть примерно в каждом из 100+, вероятно придется вносить некоторые правки, связанные с правами доступа.

Первой мыслью было пройтись по всем компонентам, заинжектить через DI UserService, прокинуть в шаблон контекст пользователя, и там с этим как-то разбираться. Однако, на счастье, я не успел этим заняться, так как нашел другое, как мне кажется более удачное решение.

exportAs

Я подумал о том, как можно сразу в шаблонах получить userContext, без необходимости инжектировать сервис или токен с пользователем в контроллерах компонент. На счастье вспомнилась весьма удобная штучка: свойство директив (ну и компонент, как наследников, естественно) - exportAs.

Что же дает exportAs?

Вот что нам сообщает документация:

exportAs: Определяет имя, которое может быть использовано в шаблоне для назначения этой директивы переменной.

Как же этим воспользоваться?

Допустим, у нас есть сервис, который авторизовался и хранит данные о пользователе.

@Injectable({
  providedIn: 'root',
})
export class UserService {
  private user$: User;

  set user(user: User) {
    this.user$ = user;
  }

  get user() {
    return this.user$;
  }

  constructor(
    private _http: HttpClientWrapperService
  ) {
  }
  
  ...
}

И есть класс, описывающий пользователя

export class User {
  id: number;
  name: string;
  claims: Claim[];

  get roles() {
    return this.claims?.map(_ => _.code);
  }

  get isAdmin() {
    return this.roles?.indexOf('ADMIN') >= 0;
  }

  get isUser() {
    return this.isAdmin || this.roles?.indexOf('USER') >= 0;
  }

  get isGuest() {
    return this.isAdmin || this.isUser || this.roles?.indexOf('GUEST') >= 0;
  }
  ...
}

Что нам нужно - создать директиву, которая будет иметь доступ к пользователю, например такую:

import {Directive} from '@angular/core';
import {UserService} from '../../service/user.service';

@Directive({
  selector: '[appUserContext]',
  exportAs: 'userContext'
})
export class UserContextDirective {
  get user() {
    return this._user?.user;
  }

  constructor(private _user: UserService) {
  }
}

И использовать ее по надобности в шаблонах приложения. Например так, если добавлять у удалять объект может только администратор.

<div class="column content-block" appUserContext #user="userContext">
	<app-toolbar>
		<ng-container *ngIf="user.user?.isAdmin">
			<button app-button (click)="add()">
				<app-icon [name]="'add'"></teta-icon>
			</button>
			<button app-button (click)="delete()">
				<teta-icon [name]="'delete'"></teta-icon>
			</button>
		</ng-container>
	</app-toolbar>
</div>

Также данная функция может использована для получения доступа к директивам и компонентам в шаблоне, без необходимости использовать ViewChild там, где для этого нет большой необходимости.

Спасибо за внимание, надеюсь эта информация окажется для вас полезной и сэкономит кому-то время.