Last active
July 8, 2021 14:33
-
-
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
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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