Skip to content

Commit

Permalink
feat: extend the role-toggle service and not-role-toggle directive to…
Browse files Browse the repository at this point in the history
… check against multiple roles if one is included

- explicitly trigger the change detection if the directive result changes (fix to apply UI changes after login/logout)
  • Loading branch information
shauke committed Apr 1, 2021
1 parent edd1d0a commit 4a8f7e3
Show file tree
Hide file tree
Showing 5 changed files with 35 additions and 9 deletions.
10 changes: 10 additions & 0 deletions src/app/core/directives/not-role-toggle.directive.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,8 @@ import { RoleToggleModule } from 'ish-core/role-toggle.module';
<div *ishHasNotRole="'ROLE1'">content1</div>
<div *ishHasNotRole="'ROLE2'">content2</div>
<div *ishHasNotRole="dynamicRole">content3</div>
<div *ishHasNotRole="['ROLE1', 'ROLE3']">content4</div>
<div *ishHasNotRole="['ROLE2', 'ROLE3']">content5</div>
`,
// Default change detection for dynamic role test
changeDetection: ChangeDetectionStrategy.Default,
Expand Down Expand Up @@ -73,4 +75,12 @@ describe('Not Role Toggle Directive', () => {

expect(element.textContent).toContain('content3');
});

it('should not render content if role is in given roles', () => {
expect(element.textContent).not.toContain('content4');
});

it('should render content if the role is not in given roles', () => {
expect(element.textContent).toContain('content5');
});
});
14 changes: 10 additions & 4 deletions src/app/core/directives/not-role-toggle.directive.ts
Original file line number Diff line number Diff line change
@@ -1,17 +1,21 @@
import { Directive, Input, OnDestroy, TemplateRef, ViewContainerRef } from '@angular/core';
import { ChangeDetectorRef, Directive, Input, OnDestroy, TemplateRef, ViewContainerRef } from '@angular/core';
import { ReplaySubject, Subject, Subscription } from 'rxjs';
import { distinctUntilChanged, takeUntil } from 'rxjs/operators';

import { RoleToggleService } from 'ish-core/utils/role-toggle/role-toggle.service';

/**
* Structural directive.
* Used on an element, this element will only be rendered if the logged in user has NOT the specified role.
* Used on an element, this element will only be rendered if the logged in user has NOT the specified role or one of the specified roles.
*
* @example
* <div *ishHasNotRole="'APP_B2B_OCI_USER'">
* Only visible when the current user is not an OCI punchout user.
* </div>
* or
* <div *ishHasNotRole="['APP_B2B_CXML_USER', 'APP_B2B_OCI_USER']">
* Only visible when the current user is not an OCI punchout user.
* </div>
*/
@Directive({
selector: '[ishHasNotRole]',
Expand All @@ -25,18 +29,20 @@ export class NotRoleToggleDirective implements OnDestroy {
constructor(
private templateRef: TemplateRef<unknown>,
private viewContainer: ViewContainerRef,
private roleToggleService: RoleToggleService
private roleToggleService: RoleToggleService,
private cdRef: ChangeDetectorRef
) {
this.enabled$.pipe(distinctUntilChanged(), takeUntil(this.destroy$)).subscribe(enabled => {
if (enabled) {
this.viewContainer.createEmbeddedView(this.templateRef);
} else {
this.viewContainer.clear();
}
this.cdRef.markForCheck();
});
}

@Input() set ishHasNotRole(roleId: string) {
@Input() set ishHasNotRole(roleId: string | string[]) {
// end previous subscription and newly subscribe
if (this.subscription) {
// tslint:disable-next-line: ban
Expand Down
2 changes: 1 addition & 1 deletion src/app/core/role-toggle.module.ts
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ export class RoleToggleModule {
{
provide: RoleToggleService,
useValue: {
hasRole: (roleId: string) =>
hasRole: (roleId: string | string[]) =>
RoleToggleModule.roleIds.pipe(
whenTruthy(),
map(roles => checkRole(roles, roleId))
Expand Down
8 changes: 8 additions & 0 deletions src/app/core/utils/role-toggle/role-toggle.service.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -35,5 +35,13 @@ describe('Role Toggle Service', () => {
it("should return false if user doesn't have the role", () => {
expect(roleToggleService.hasRole('ROLE2')).toBeObservable(cold('a', { a: false }));
});

it('should return true if user has one of the roles', () => {
expect(roleToggleService.hasRole(['ROLE1', 'ROLE3'])).toBeObservable(cold('a', { a: true }));
});

it("should return false if user doesn't have one the roles", () => {
expect(roleToggleService.hasRole(['ROLE2', 'ROLE3'])).toBeObservable(cold('a', { a: false }));
});
});
});
10 changes: 6 additions & 4 deletions src/app/core/utils/role-toggle/role-toggle.service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,8 +6,10 @@ import { map } from 'rxjs/operators';
import { getUserRoles } from 'ish-core/store/customer/authorization';
import { whenTruthy } from 'ish-core/utils/operators';

export function checkRole(roleIds: string[], roleId: string): boolean {
return roleIds.includes(roleId);
export function checkRole(roleIds: string[], roleId: string | string[]): boolean {
// tslint:disable-next-line: no-parameter-reassignment
roleId = typeof roleId === 'string' ? [roleId] : typeof roleId === 'undefined' ? [] : roleId;
return roleId.some(id => roleIds.includes(id));
}

@Injectable({ providedIn: 'root' })
Expand All @@ -18,9 +20,9 @@ export class RoleToggleService {
this.roleIds$ = store.pipe(select(getUserRoles)).pipe(map(roles => roles.map(role => role.roleId)));
}

hasRole(roleId: string): Observable<boolean> {
hasRole(roleId: string | string[]): Observable<boolean> {
return this.roleIds$.pipe(
// wait for permissions to be loaded
// wait for user roles to be loaded
whenTruthy(),
map(roleIds => checkRole(roleIds, roleId))
);
Expand Down

0 comments on commit 4a8f7e3

Please sign in to comment.