I have an Observable that is backed by a private BehaviorSubject. On the first emit, I would like to initialize the BehaviorSubject from an async call, but I can't come up with a great pattern to do so. As far as I can tell, BehaviorSubjects can't be initialized from an async function.
What I have so far is:
protected _monkeyNames = new BehaviorSubject<Set<string>>(null);
MonkeyNames$: Observable<Set<string>> = this._monkeyNames.pipe(
switchMap(async (nodes) => nodes ?? (await this.getMonkeyNames()))
);
protected async getMonkeyNames(): Promise<Set<string>> {
const names = new Set(await this.stateService.getMonkeyNames());
return names;
}
But this won't set the BehaviorSubject, it will only be set when I call setMonkeyNames
later to save a new value. If I do call .next()
inside of getMonkeyNames
, the Observable will emit again, which could result in an eternal loop if names
is null
.
It might be my own ignorance when it comes to rxjs, but does anyone have a pattern they use for this?
Edit:
I should mention that this is a service, and I won't have access to ngOnInit()
CodePudding user response:
BehaviorSubject
is useful only when there is a initial value. Since you don't have any, ReplaySubject
or Subject
can be used. Also initializing BehaviorSubject with null, when type clearly restricts to Set<...>, is just an error.
If your service returns promise of some value, convert it to Observable with from
and process in operator chain.
monkeyNames$: Observable<Set<string>> = from(this.stateService.getMonkeyNames())
.pipe(
map(names=> new Set(names))
)
CodePudding user response:
Instead of attempting to initialize the subject, maybe you can declare your MonkeyNames$
from two different sources using merge
:
protected _monkeyNames = new Subject<Set<string>>();
MonkeyNames$: Observable<Set<string>> = merge(
from(this.getMonkeyNames()),
this._monkeyNames
).pipe(
shareReplay(1)
);
CodePudding user response:
What you can do is initialize the BehaviorSubject
with an empty Set
, then do the api call or async call in the constructor of the service that would set the initial value for the first time.
protected _monkeyNames = new BehaviorSubject<Set<string>>(new Set());
MonkeyNames$: Observable<Set<string>> = this._monkeyNames.asObservable();
protected async getMonkeyNames(): Promise<Set<string>> {
const names = new Set(await this.stateService.getMonkeyNames());
this._monkeyNames.next(names);
return names;
}
constructor() {
this.getMonkeyNames();
}
Example HTML Template:
<div *ngIf="MonkeyNames$ | async as names">
<div *ngFor="let name of names">{{ name }}</div>
</div>
Here is a working Stackblitz example (with some extra debugging; see console).