libs/acl/src/lib/acl-tag/acl-tag.ts
Core element of our hierarchical acl tag toolkit. Each time we find an element with a aclTag the bound directive (AclTagDirective) creates an instance of this AclTag and injects it as 'ACL_TAG_TOKEN'.
The created instance has a binding to the property 'aclTag' of the directive and to any parent AclTag which builds up the expected hierarchy.
Properties |
aclKey | |
Type |
string
|
ownKey | |
Type |
string
|
parentKeys | |
Type |
string[]
|
import { BehaviorSubject, combineLatest, ReplaySubject, Subject } from 'rxjs';
import { startWith, takeUntil, tap } from 'rxjs/operators';
/**
* Core element of our hierarchical acl tag toolkit.
* Each time we find an element with a aclTag the bound directive (AclTagDirective)
* creates an instance of this AclTag and injects it as 'ACL_TAG_TOKEN'.
*
* The created instance has a binding to the property 'aclTag' of the directive
* and to any parent AclTag which builds up the expected hierarchy.
*/
export interface AclTagItem {
ownKey: string;
parentKeys: string[];
aclKey: string /** full qualified path **/;
}
export class AclTag {
tagName$: BehaviorSubject<string>;
transient$: BehaviorSubject<boolean> = new BehaviorSubject<boolean>(false);
_aclKeySubject: ReplaySubject<AclTagItem> = new ReplaySubject<AclTagItem>(1);
private _changed$ = new Subject<void>();
private _destroySignal$ = new Subject<void>();
static joinKeys(...keys: string[]) {
return keys.join('/');
}
constructor(
tagName: BehaviorSubject<string> | string,
private parent?: AclTag,
transient?: BehaviorSubject<boolean>
) {
if (typeof tagName === 'string') {
tagName = new BehaviorSubject(tagName);
}
if (transient) {
this.transient$ = transient;
}
this.tagName$ = tagName;
this.subscribeToChanges();
}
get aclKey$() {
return this._aclKeySubject.asObservable().pipe(takeUntil(this._destroySignal$));
}
destroy() {
this._destroySignal$.next();
}
get changed$() {
return this._changed$.asObservable();
}
/**
* wait for parent changes or own tag name changes and
* calculate tag name for each change
*/
subscribeToChanges() {
const parentList = (this.parent?.changed$ ?? new Subject<void>()).pipe(startWith([null]));
combineLatest([this.tagName$, parentList, this.transient$])
.pipe(
tap(() => {
this.update();
}),
takeUntil(this._destroySignal$)
)
.subscribe();
}
notifyChildren() {
this._changed$.next();
}
update() {
// write current acl key intro the stream
this._aclKeySubject.next({
aclKey: this.aclKey,
ownKey: this.ownTag,
parentKeys: this.parentList
});
// notify dependent acl tags
this.notifyChildren();
}
get aclKey() {
if (this.isTransient) {
return AclTag.joinKeys(...this.parentList, this.ownTag);
}
return AclTag.joinKeys(...this.tagList);
}
get ownTag() {
return this.tagName$.getValue();
}
get isTransient() {
return this.transient$.getValue();
}
get tagList(): string[] {
if (this.isTransient) {
return this.parentList;
}
return this.parentList.concat(this.ownTag);
}
get parentList() {
return [...(this.parent?.tagList ?? [])];
}
}