diff --git a/apps/angular/6-structural-directive/src/app/dashboard/admin.component.ts b/apps/angular/6-structural-directive/src/app/dashboard/admin.component.ts
index 26bb23284..e403562e0 100644
--- a/apps/angular/6-structural-directive/src/app/dashboard/admin.component.ts
+++ b/apps/angular/6-structural-directive/src/app/dashboard/admin.component.ts
@@ -3,12 +3,12 @@ import { RouterLink } from '@angular/router';
import { ButtonComponent } from '../button.component';
@Component({
- selector: 'app-dashboard',
imports: [RouterLink, ButtonComponent],
template: `
dashboard for Admin works!
`,
changeDetection: ChangeDetectionStrategy.OnPush,
+ host: { hostID: crypto.randomUUID().toString() },
})
-export class AdminDashboardComponent {}
+export default class AdminDashboardComponent {}
diff --git a/apps/angular/6-structural-directive/src/app/dashboard/client.component.ts b/apps/angular/6-structural-directive/src/app/dashboard/client.component.ts
new file mode 100644
index 000000000..d334ce7d0
--- /dev/null
+++ b/apps/angular/6-structural-directive/src/app/dashboard/client.component.ts
@@ -0,0 +1,14 @@
+import { ChangeDetectionStrategy, Component } from '@angular/core';
+import { RouterLink } from '@angular/router';
+import { ButtonComponent } from '../button.component';
+
+@Component({
+ imports: [RouterLink, ButtonComponent],
+ template: `
+ dashboard for Client works!
+
+ `,
+ changeDetection: ChangeDetectionStrategy.OnPush,
+ host: { hostID: crypto.randomUUID().toString() },
+})
+export default class ClientDashboardComponent {}
diff --git a/apps/angular/6-structural-directive/src/app/dashboard/everyone.component.ts b/apps/angular/6-structural-directive/src/app/dashboard/everyone.component.ts
new file mode 100644
index 000000000..768577cfc
--- /dev/null
+++ b/apps/angular/6-structural-directive/src/app/dashboard/everyone.component.ts
@@ -0,0 +1,14 @@
+import { ChangeDetectionStrategy, Component } from '@angular/core';
+import { RouterLink } from '@angular/router';
+import { ButtonComponent } from '../button.component';
+
+@Component({
+ imports: [RouterLink, ButtonComponent],
+ template: `
+ dashboard for Everyone works!
+
+ `,
+ changeDetection: ChangeDetectionStrategy.OnPush,
+ host: { hostID: crypto.randomUUID().toString() },
+})
+export default class EveryoneDashboardComponent {}
diff --git a/apps/angular/6-structural-directive/src/app/dashboard/manager.component.ts b/apps/angular/6-structural-directive/src/app/dashboard/manager.component.ts
index 60ea7695b..1cbc029d2 100644
--- a/apps/angular/6-structural-directive/src/app/dashboard/manager.component.ts
+++ b/apps/angular/6-structural-directive/src/app/dashboard/manager.component.ts
@@ -1,12 +1,14 @@
import { ChangeDetectionStrategy, Component } from '@angular/core';
+import { RouterLink } from '@angular/router';
+import { ButtonComponent } from '../button.component';
@Component({
- selector: 'app-dashboard',
- imports: [],
+ imports: [RouterLink, ButtonComponent],
template: `
dashboard for Manager works!
`,
changeDetection: ChangeDetectionStrategy.OnPush,
+ host: { hostID: crypto.randomUUID().toString() },
})
-export class ManagerDashboardComponent {}
+export default class ManagerDashboardComponent {}
diff --git a/apps/angular/6-structural-directive/src/app/dashboard/reader-writer.component.ts b/apps/angular/6-structural-directive/src/app/dashboard/reader-writer.component.ts
new file mode 100644
index 000000000..9e75843f4
--- /dev/null
+++ b/apps/angular/6-structural-directive/src/app/dashboard/reader-writer.component.ts
@@ -0,0 +1,14 @@
+import { ChangeDetectionStrategy, Component } from '@angular/core';
+import { RouterLink } from '@angular/router';
+import { ButtonComponent } from '../button.component';
+
+@Component({
+ imports: [RouterLink, ButtonComponent],
+ template: `
+ dashboard for Reader/Writer works!
+
+ `,
+ changeDetection: ChangeDetectionStrategy.OnPush,
+ host: { hostID: crypto.randomUUID().toString() },
+})
+export default class ReaderWriterDashboardComponent {}
diff --git a/apps/angular/6-structural-directive/src/app/hasRole.directive.ts b/apps/angular/6-structural-directive/src/app/hasRole.directive.ts
new file mode 100644
index 000000000..1e44a8758
--- /dev/null
+++ b/apps/angular/6-structural-directive/src/app/hasRole.directive.ts
@@ -0,0 +1,60 @@
+import {
+ booleanAttribute,
+ computed,
+ Directive,
+ effect,
+ inject,
+ input,
+ TemplateRef,
+ ViewContainerRef,
+} from '@angular/core';
+import { Role } from './user.model';
+import { UserStore } from './user.store';
+
+@Directive({
+ selector: '[hasRole], [hasRoleSuperAdmin]',
+ standalone: true,
+})
+export class HasRoleDirective {
+ private templateRef = inject(TemplateRef);
+ private viewContainer = inject(ViewContainerRef);
+ private userStore = inject(UserStore);
+
+ role = input(null, { alias: 'hasRole' });
+ isAdmin = input(false, {
+ alias: 'hasRoleSuperAdmin',
+ transform: booleanAttribute,
+ });
+
+ private hasAccess = computed(() => {
+ const isAdminFlag = this.isAdmin();
+ const role = normalizeRoles(this.role());
+
+ if (isAdminFlag) {
+ return this.userStore.isAdmin();
+ }
+
+ if (role) {
+ return this.userStore.hasAnyRole(role)();
+ }
+ return false;
+ });
+
+ private _effect = effect(() =>
+ this.hasAccess() ? this.addTemplate() : this.clearTemplate(),
+ );
+
+ private addTemplate() {
+ this.viewContainer.clear();
+ this.viewContainer.createEmbeddedView(this.templateRef);
+ }
+
+ private clearTemplate() {
+ this.viewContainer.clear();
+ }
+}
+
+function normalizeRoles(role: Role | Role[] | null): Role[] | null {
+ if (!role) return null;
+ return Array.isArray(role) ? role : [role];
+}
diff --git a/apps/angular/6-structural-directive/src/app/hasRole.guard.ts b/apps/angular/6-structural-directive/src/app/hasRole.guard.ts
new file mode 100644
index 000000000..40c0dbc06
--- /dev/null
+++ b/apps/angular/6-structural-directive/src/app/hasRole.guard.ts
@@ -0,0 +1,8 @@
+import { inject } from '@angular/core';
+import { Role } from './user.model';
+import { UserStore } from './user.store';
+
+export const hasRoleGuard = (role: Role[]) => {
+ const userStore = inject(UserStore);
+ return userStore.hasAnyRole(role)();
+};
diff --git a/apps/angular/6-structural-directive/src/app/information.component.ts b/apps/angular/6-structural-directive/src/app/information.component.ts
index ecf937efc..4f8ab3f56 100644
--- a/apps/angular/6-structural-directive/src/app/information.component.ts
+++ b/apps/angular/6-structural-directive/src/app/information.component.ts
@@ -1,22 +1,19 @@
-import { ChangeDetectionStrategy, Component, inject } from '@angular/core';
-import { UserStore } from './user.store';
+import { ChangeDetectionStrategy, Component } from '@angular/core';
+import { HasRoleDirective } from './hasRole.directive';
@Component({
selector: 'app-information',
template: `
Information Panel
- visible only for super admin
- visible if manager
- visible if manager and/or reader
- visible if manager and/or writer
- visible if client
+ visible only for super admin
+ visible if manager
+ visible if manager and/or reader
+ visible if manager and/or writer
+ visible if client
visible for everyone
`,
changeDetection: ChangeDetectionStrategy.OnPush,
+ imports: [HasRoleDirective],
})
-export class InformationComponent {
- private readonly userStore = inject(UserStore);
-
- user$ = this.userStore.user$;
-}
+export class InformationComponent {}
diff --git a/apps/angular/6-structural-directive/src/app/isAdmin.guard.ts b/apps/angular/6-structural-directive/src/app/isAdmin.guard.ts
new file mode 100644
index 000000000..05780339b
--- /dev/null
+++ b/apps/angular/6-structural-directive/src/app/isAdmin.guard.ts
@@ -0,0 +1,8 @@
+import { inject } from '@angular/core';
+import { CanMatchFn } from '@angular/router';
+import { UserStore } from './user.store';
+
+export const isAdminGuard: CanMatchFn = () => {
+ const userStore = inject(UserStore);
+ return userStore.isAdmin();
+};
diff --git a/apps/angular/6-structural-directive/src/app/routes.ts b/apps/angular/6-structural-directive/src/app/routes.ts
index 4db203f3b..d7f555d7c 100644
--- a/apps/angular/6-structural-directive/src/app/routes.ts
+++ b/apps/angular/6-structural-directive/src/app/routes.ts
@@ -1,3 +1,6 @@
+import { hasRoleGuard } from './hasRole.guard';
+import { isAdminGuard } from './isAdmin.guard';
+
export const APP_ROUTES = [
{
path: '',
@@ -6,9 +9,26 @@ export const APP_ROUTES = [
},
{
path: 'enter',
- loadComponent: () =>
- import('./dashboard/admin.component').then(
- (m) => m.AdminDashboardComponent,
- ),
+ canMatch: [isAdminGuard],
+ loadComponent: () => import('./dashboard/admin.component'),
+ },
+ {
+ path: 'enter',
+ canMatch: [() => hasRoleGuard(['MANAGER'])],
+ loadComponent: () => import('./dashboard/manager.component'),
+ },
+ {
+ path: 'enter',
+ canMatch: [() => hasRoleGuard(['CLIENT'])],
+ loadComponent: () => import('./dashboard/client.component'),
+ },
+ {
+ path: 'enter',
+ canMatch: [() => hasRoleGuard(['READER', 'WRITER'])],
+ loadComponent: () => import('./dashboard/reader-writer.component'),
+ },
+ {
+ path: 'enter',
+ loadComponent: () => import('./dashboard/everyone.component'),
},
];
diff --git a/apps/angular/6-structural-directive/src/app/user.store.ts b/apps/angular/6-structural-directive/src/app/user.store.ts
index 1b00288b7..c2b2d5c3f 100644
--- a/apps/angular/6-structural-directive/src/app/user.store.ts
+++ b/apps/angular/6-structural-directive/src/app/user.store.ts
@@ -1,15 +1,25 @@
-import { Injectable } from '@angular/core';
-import { BehaviorSubject } from 'rxjs';
-import { User } from './user.model';
+import { computed, Injectable, signal } from '@angular/core';
+import { Role, User } from './user.model';
@Injectable({
providedIn: 'root',
})
export class UserStore {
- private user = new BehaviorSubject(undefined);
- user$ = this.user.asObservable();
+ private _user = signal(undefined);
+ readonly user = this._user.asReadonly();
add(user: User) {
- this.user.next(user);
+ this._user.set(user);
}
+
+ isAdmin = computed(() => !!this.user()?.isAdmin);
+
+ hasAnyRole = (role: Role | Role[]) => {
+ return computed(() => {
+ const user = this.user();
+ if (user?.isAdmin) return true;
+ const roles: Role[] = Array.isArray(role) ? role : [role];
+ return roles.length === 0 || user?.roles.some((r) => roles.includes(r));
+ });
+ };
}