Skip to content

Instantly share code, notes, and snippets.

@dmorosinotto
Last active July 8, 2021 14:33
Show Gist options
  • Save dmorosinotto/06545ed9e543adf196a5963a88448eeb to your computer and use it in GitHub Desktop.
Save dmorosinotto/06545ed9e543adf196a5963a88448eeb to your computer and use it in GitHub Desktop.
Angular Context.Service used to share data (aka state) across components - It's a "dynamic reactive DI" - sample usage: https://stackblitz.com/edit/angular-context-service
import { Injectable, OnDestroy, Optional, SkipSelf } from "@angular/core";
import { Observable, Observer, BehaviorSubject, Subject } from "rxjs";
import { takeUntil, filter } from "rxjs/operators";
import { InjectionToken } from "@angular/core";
@Injectable()
export class ContextService implements OnDestroy {
constructor(@Optional() @SkipSelf() private _parent?: ContextService) {}
private _destroy$ = new Subject<void>();
ngOnDestroy(): void {
this._parent = undefined;
this._destroy$.next();
this._destroy$.complete();
//cleanup internals I don't know if it's safer to execute this in nextTick Promise.resolve().then()
for (let _key in this._) (this._[_key] as unknown) = undefined;
}
private _: {
//internals use an object map string -> BehaviorSubject (ndr token -> reactive variable)
[_key: string]: BehaviorSubject<unknown | undefined>;
} = {};
private _bs<T>(key: string | InjectionToken<T>): BehaviorSubject<T | undefined> | null {
//recursive get the internal BehaviorSubject from the local _ map, or clib the _parent hierarchy
const _key = key.toString(); //directly use string or get it from InjectionToken
if (_key in this._) return this._[_key] as BehaviorSubject<T | undefined>;
else if (this._parent != null) return this._parent._bs(key);
else return null;
}
provide<T>(key: string | InjectionToken<T>, initVal?: T): Observer<T> {
let _bs = this._bs<T>(key);
if (_bs == null) _bs = this._[key.toString()] = new BehaviorSubject<T | undefined>(initVal);
else _bs.next(initVal); //if no initVal is passed it emits undefined but it's filtered
return {
next: _bs.next.bind(_bs),
error: _bs.error.bind(_bs),
complete: _bs.complete.bind(_bs)
};
}
get$<T>(key: string | InjectionToken<T>): Observable<T> {
let _bs = this._bs<T>(key);
if (_bs == null) _bs = this._[key.toString()] = new BehaviorSubject<T | undefined>(undefined);
return _bs.pipe(
filter(t => t !== undefined), //filter the initial undefined, or possible initVal not passed
takeUntil(this._destroy$)
);
}
getCurr<T>(key: string | InjectionToken<T>): T | undefined {
const _bs = this._bs<T>(key);
if (_bs == null) return undefined;
else return _bs.getValue();
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment