File

libs/acl/src/lib/acl-tag/acl-tag.ts

Description

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.

Index

Properties

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 ?? [])];
  }
}

results matching ""

    No results matching ""