Skip to content

Answer:6 directives and guard for permissions (guard with injectable service) #33

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Closed
wants to merge 1 commit into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion apps/permissions/project.json
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,8 @@
"apps/permissions/src/assets"
],
"styles": ["apps/permissions/src/styles.scss"],
"scripts": []
"scripts": [],
"allowedCommonJsDependencies": ["seedrandom"]
},
"configurations": {
"production": {
Expand Down
3 changes: 1 addition & 2 deletions apps/permissions/src/app/dashboard/admin.component.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,6 @@ import { RouterLink } from '@angular/router';
import { ButtonComponent } from '../button.component';

@Component({
selector: 'app-dashboard',
standalone: true,
imports: [RouterLink, ButtonComponent],
template: `
Expand All @@ -12,4 +11,4 @@ import { ButtonComponent } from '../button.component';
`,
changeDetection: ChangeDetectionStrategy.OnPush,
})
export class AdminDashboardComponent {}
export default class AdminDashboardComponent {}
14 changes: 14 additions & 0 deletions apps/permissions/src/app/dashboard/client.component.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
import { ChangeDetectionStrategy, Component } from '@angular/core';
import { RouterLink } from '@angular/router';
import { ButtonComponent } from '../button.component';

@Component({
standalone: true,
imports: [RouterLink, ButtonComponent],
template: `
<p>dashboard for Client works!</p>
<button app-button routerLink="/">Logout</button>
`,
changeDetection: ChangeDetectionStrategy.OnPush,
})
export default class ClientDashboardComponent {}
14 changes: 14 additions & 0 deletions apps/permissions/src/app/dashboard/everyone.component.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
import { ChangeDetectionStrategy, Component } from '@angular/core';
import { RouterLink } from '@angular/router';
import { ButtonComponent } from '../button.component';

@Component({
standalone: true,
imports: [RouterLink, ButtonComponent],
template: `
<p>dashboard for Everyone!</p>
<button app-button routerLink="/">Logout</button>
`,
changeDetection: ChangeDetectionStrategy.OnPush,
})
export default class EveryoneDashboardComponent {}
3 changes: 1 addition & 2 deletions apps/permissions/src/app/dashboard/manager.component.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,6 @@ import { RouterLink } from '@angular/router';
import { ButtonComponent } from '../button.component';

@Component({
selector: 'app-dashboard',
standalone: true,
imports: [RouterLink, ButtonComponent],
template: `
Expand All @@ -12,4 +11,4 @@ import { ButtonComponent } from '../button.component';
`,
changeDetection: ChangeDetectionStrategy.OnPush,
})
export class ManagerDashboardComponent {}
export default class ManagerDashboardComponent {}
14 changes: 14 additions & 0 deletions apps/permissions/src/app/dashboard/no-user.component.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
import { ChangeDetectionStrategy, Component } from '@angular/core';
import { RouterLink } from '@angular/router';
import { ButtonComponent } from '../button.component';

@Component({
standalone: true,
imports: [RouterLink, ButtonComponent],
template: `
<p>User is not Logged In</p>
<button app-button routerLink="/">Logout</button>
`,
changeDetection: ChangeDetectionStrategy.OnPush,
})
export default class NoUserDashboardComponent {}
14 changes: 14 additions & 0 deletions apps/permissions/src/app/dashboard/writer-reader.component.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
import { ChangeDetectionStrategy, Component } from '@angular/core';
import { RouterLink } from '@angular/router';
import { ButtonComponent } from '../button.component';

@Component({
standalone: true,
imports: [RouterLink, ButtonComponent],
template: `
<p>dashboard for Writer or Reader works!</p>
<button app-button routerLink="/">Logout</button>
`,
changeDetection: ChangeDetectionStrategy.OnPush,
})
export default class WriterReaderDashboardComponent {}
57 changes: 57 additions & 0 deletions apps/permissions/src/app/directive/has-role.cs.directive.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
/* eslint-disable @typescript-eslint/ban-types */
/* eslint-disable @typescript-eslint/member-ordering */
/* eslint-disable @angular-eslint/directive-selector */
import {
Directive,
inject,
Input,
TemplateRef,
ViewContainerRef,
} from '@angular/core';
import { ComponentStore } from '@ngrx/component-store';
import { pipe, tap } from 'rxjs';
import { Role } from '../user.model';
import { UserStore } from '../user.store';

@Directive({
selector: '[hasRole], [hasRoleIsAdmin]',
standalone: true,
providers: [ComponentStore],
})
export class HasRoleDirective {
private templateRef = inject(TemplateRef<unknown>);
private viewContainer = inject(ViewContainerRef);
private componentStore = inject(ComponentStore);
private store = inject(UserStore);

@Input('hasRole') set role(role: Role | Role[] | undefined) {
if (role) {
this.showTemplate(this.store.hasAnyRole(role));
}
}

@Input('hasRoleIsAdmin') set isAdmin(isAdmin: boolean) {
if (isAdmin) {
this.showTemplate(this.store.isAdmin$);
}
}

private readonly showTemplate = this.componentStore.effect<
boolean | undefined
>(
pipe(
tap((showTemplate) =>
showTemplate ? this.addTemplate() : this.clearTemplate()
)
)
);

private addTemplate() {
this.viewContainer.clear();
this.viewContainer.createEmbeddedView(this.templateRef);
}

private clearTemplate() {
this.viewContainer.clear();
}
}
60 changes: 60 additions & 0 deletions apps/permissions/src/app/directive/has-role.directive.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
/* eslint-disable @angular-eslint/directive-selector */
import {
injectDestroyService,
provideDestroyService,
} from '@angular-challenges/shared/utils';
import {
Directive,
inject,
Input,
OnInit,
TemplateRef,
ViewContainerRef,
} from '@angular/core';
import { takeUntil } from 'rxjs';
import { Role } from '../user.model';
import { UserStore } from '../user.store';

@Directive({
selector: '[hasRole], [hasRoleIsAdmin]',
standalone: true,
providers: [provideDestroyService()],
})
export class HasRoleDirective implements OnInit {
private destroy$ = injectDestroyService();
private templateRef = inject(TemplateRef<unknown>);
private viewContainer = inject(ViewContainerRef);
private store = inject(UserStore);

@Input('hasRole') role: Role | Role[] | undefined = undefined;

@Input('hasRoleIsAdmin') isAdmin = false;

ngOnInit(): void {
if (this.isAdmin) {
this.store.isAdmin$
.pipe(takeUntil(this.destroy$))
.subscribe((isAdmin) =>
isAdmin ? this.addTemplate() : this.clearTemplate()
);
} else if (this.role) {
this.store
.hasAnyRole(this.role)
.pipe(takeUntil(this.destroy$))
.subscribe((hasPermission) =>
hasPermission ? this.addTemplate() : this.clearTemplate()
);
} else {
this.addTemplate();
}
}

private addTemplate() {
this.viewContainer.clear();
this.viewContainer.createEmbeddedView(this.templateRef);
}

private clearTemplate() {
this.viewContainer.clear();
}
}
42 changes: 42 additions & 0 deletions apps/permissions/src/app/directive/has-role.rx.v15.directive.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
/* eslint-disable @typescript-eslint/ban-types */
/* eslint-disable @angular-eslint/directive-selector */
import { NgIf } from '@angular/common';
import { Directive, inject, Input } from '@angular/core';
import { RxEffects } from '@rx-angular/state/effects';
import { mergeMap, Observable, Subject } from 'rxjs';
import { Role } from '../user.model';
import { UserStore } from '../user.store';

@Directive({
selector: '[hasRole], [hasRoleIsAdmin]',
standalone: true,
hostDirectives: [NgIf],
providers: [RxEffects],
})
export class HasRoleDirective {
private store = inject(UserStore);
private rxEffect = inject(RxEffects);
private ngIf = inject(NgIf, { host: true });

private show = new Subject<Observable<boolean | undefined>>();
private show$ = this.show.asObservable().pipe(mergeMap((b) => b));

@Input('hasRole') set role(role: Role | Role[] | undefined) {
if (role) {
this.show.next(this.store.hasAnyRole(role));
}
}

@Input('hasRoleIsAdmin') set isAdmin(isAdmin: boolean) {
if (isAdmin) {
this.show.next(this.store.isAdmin$);
}
}

constructor() {
this.rxEffect.register(this.show$, this.showTemplate);
}

private showTemplate = (showTemplate: boolean | undefined) =>
(this.ngIf.ngIf = showTemplate);
}
65 changes: 65 additions & 0 deletions apps/permissions/src/app/directive/has-role.rxjs.directive.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
/* eslint-disable @angular-eslint/directive-selector */

import {
injectDestroyService,
provideDestroyService,
} from '@angular-challenges/shared/utils';
import {
Directive,
inject,
Input,
OnInit,
TemplateRef,
ViewContainerRef,
} from '@angular/core';
import { BehaviorSubject, mergeMap, Observable, of, takeUntil } from 'rxjs';
import { Role } from '../user.model';
import { UserStore } from '../user.store';

@Directive({
selector: '[hasRole], [hasRoleIsAdmin]',
standalone: true,
providers: [provideDestroyService()],
})
export class HasRoleDirective implements OnInit {
private destroy$ = injectDestroyService();
private templateRef = inject(TemplateRef<unknown>);
private viewContainer = inject(ViewContainerRef);
private store = inject(UserStore);

private show = new BehaviorSubject<Observable<boolean | undefined>>(
of(undefined)
);

@Input('hasRole') set role(role: Role | Role[] | undefined) {
if (role) {
this.show.next(this.store.hasAnyRole(role));
}
}

@Input('hasRoleIsAdmin') set isAdmin(isAdmin: boolean) {
if (isAdmin) {
this.show.next(this.store.isAdmin$);
}
}

ngOnInit(): void {
this.show
.pipe(
mergeMap((s) => s),
takeUntil(this.destroy$)
)
.subscribe((showTemplate) =>
showTemplate ? this.addTemplate() : this.clearTemplate()
);
}

private addTemplate() {
this.viewContainer.clear();
this.viewContainer.createEmbeddedView(this.templateRef);
}

private clearTemplate() {
this.viewContainer.clear();
}
}
36 changes: 36 additions & 0 deletions apps/permissions/src/app/directive/has-role.v15.directive.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
/* eslint-disable @typescript-eslint/ban-types */
/* eslint-disable @angular-eslint/directive-selector */
import { NgIf } from '@angular/common';
import { Directive, inject, Input } from '@angular/core';
import { ComponentStore } from '@ngrx/component-store';
import { pipe, tap } from 'rxjs';
import { Role } from '../user.model';
import { UserStore } from '../user.store';

@Directive({
selector: '[hasRole], [hasRoleIsAdmin]',
standalone: true,
hostDirectives: [NgIf],
providers: [ComponentStore],
})
export class HasRoleDirective {
private store = inject(UserStore);
private componentStore = inject(ComponentStore);
private ngIf = inject(NgIf, { host: true });

@Input('hasRole') set role(role: Role | Role[] | undefined) {
if (role) {
this.showTemplate(this.store.hasAnyRole(role));
}
}

@Input('hasRoleIsAdmin') set isAdmin(isAdmin: boolean) {
if (isAdmin) {
this.showTemplate(this.store.isAdmin$);
}
}

private readonly showTemplate = this.componentStore.effect<
boolean | undefined
>(pipe(tap((showTemplate) => (this.ngIf.ngIf = showTemplate))));
}
36 changes: 36 additions & 0 deletions apps/permissions/src/app/has-permission.guard.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
import { inject, Injectable } from '@angular/core';
import { CanMatch, Route, Router, UrlTree } from '@angular/router';
import { map, mergeMap, Observable, of } from 'rxjs';
import { Role } from './user.model';
import { UserStore } from './user.store';

@Injectable({ providedIn: 'root' })
export class HasPermissionGuard implements CanMatch {
private router = inject(Router);
private userStore = inject(UserStore);

canMatch(route: Route): Observable<boolean | UrlTree> {
const accessRolesList: Role[] = route.data?.['roles'] ?? [];
const isAdmin: boolean = route.data?.['isAdmin'] ?? false;
return this.hasPermission$(isAdmin, accessRolesList);
}

private hasPermission$(isAdmin: boolean, accessRolesList: Role[]) {
return this.userStore.isUserLoggedIn$.pipe(
mergeMap((hasUser) => {
if (hasUser) {
if (isAdmin) {
return this.userStore.isAdmin$.pipe(map(Boolean));
} else if (accessRolesList.length > 0) {
return this.userStore
.hasAnyRole(accessRolesList)
.pipe(map(Boolean));
}
return of(false);
} else {
return of(this.router.parseUrl('no-user'));
}
})
);
}
}
Loading