Having read that you should never subscribe within another observable, I am having major difficulties understanding how to properly handle nested observables.
For every Candidate emitted, I want to match against multiple regular expressions, provided as an observable getPatterns$(). If one is found, it will be attached to the Candidate object.
class Candidate {
public name: string;
public matchingRegularExpression: RegExp;
}
listOfCandidates = [new Candidate('one'), new Candidate('two')];
private matchCandidates$(textToMatch: string): Observable<Candidate> {
return from(this.listOfCandidates)
.pipe(
map(f => {
f.regExp = this.getRegExp(f); // sequential
return f;
}),
map(cand: Candidate => {
this.getPatterns$().subscribe(patterns => {
if (....do some regexp matching...){
cand.matchingRegularExpression = pattern;
}
});
})
)
I tried using mergeMap or switchMap, but those seem to be used when you want to merge 1 outer object with n inner ones. But my inner observable should simply extend my Candidate object and emit 2 values in this example.
So first, it's considered as a bad practice because you completely lose the reference of your second subscription, which could result in a leak if the source continue emitting.
Fortunately we have an operator called switchMap, it allows us to switch on a new observable, and thus, avoid subscribing inside the observer function.
The main difference between switchMap and other flattening operators is the cancelling effect. On each emission the previous inner observable (the result of the function you supplied) is cancelled and the new observable is subscribed. You can remember this by the phrase switch to a new observable.
edit: add snippet
class Candidate {
public name: string;
public matchingRegularExpression: RegExp;
}
listOfCandidates = [new Candidate('one'), new Candidate('two')];
private matchCandidates$(textToMatch: string): Observable<Candidate> {
return from(this.listOfCandidates)
.pipe(
map(f => {
f.regExp = this.getRegExp(f); // sequential
return f;
}),
switchMap(cand: Candidate => {
return this.getPatterns$().pipe(
map(patterns => {
if (....do some regexp matching...){
cand.matchingRegularExpression = pattern;
}
return cand
})
)})
)
Related
I asked a question
Is Observable from chained promises equivalent of observables created with from and chained with concatMap?
on totally false premises. It seems that neither of my solutions had nothing to do with my intention.
I created a method that returns Observable and calls 2 methods returning Promise. I tried 2 ways:
public setItemInfos(itemInfos: IItemInfo[]): Observable<number> {
return from(this.db.selectionItemInfos.clear().then(() => {
return this.db.selectionItemInfos.bulkAdd(itemInfos);
}));
}
public setItemInfos(itemInfos: IItemInfo[]): Observable<number> {
const clear$ = from(this.db.selectionItemInfos.clear());
const bulkAdd$ = from(this.db.selectionItemInfos.bulkAdd(itemInfos));
return clear$.pipe(concatMap(() => bulkAdd$))
}
the use will be:
myService.setItemInfos(itemInfos).subsribe(count => {
console.log(`Cleared the table 1st and then added ${count} new items`);
});
I thought from both versions that:
table clear is execution is finished when bulkAdd starts
when bulkAdd is finished i get the count from that in subscribe
How this should really be done? Or can it be done?
This is (from what I can tell here), how I would do it.
In general, defer (or any higher-order operator) is a better way to create an observable from a promise. Defer lets you take the eager evaluation semantics of promises and turn them into the lazy evaluation semantics of observables.
Then all the usual observable operators and such will function as expected.
public setItemInfos(itemInfos: IItemInfo[]): Observable<number> {
const clear$ = defer(() => this.db.selectionItemInfos.clear());
const bulkAdd$ = defer(() => this.db.selectionItemInfos.bulkAdd(itemInfos));
return concat(clear$, bulkAdd$);
}
Update 1:
So I think I might know what you're after. This isn't really idiomatic RxJS since it's such an interleaving mix of declarative, imperative style of code. Even so, this should work? I haven't tested it fully, but some tinkering and I think this should do what you're after.
There's most assuredly a better way to accomplish the same thing, but without seeing the bigger picture of what you're after, it's hard to say.
interface Tagged<T> {
payload: T,
tag: number
}
class abitraryClass{
private setItemInfoSub: Subject<Tagged<IItemInfo[]>>;
private processItemInfo: Observable<Tagged<number>>;
private itemInfoTag = 0;
constructor(){
this.setItemInfoSub = new Subject<Tagged<IItemInfo[]>>();
this.processItemInfo = this.setItemInfoSub.pipe(
concatMap(({tag, payload: itemInfos}) => this.db.selectionItemInfos.clear().pipe(
ignoreElements(),
concatWith(defer(() => this.db.selectionItemInfos.bulkAdd(itemInfos))),
map(response => ({
payload: response,
tag
}))
)),
shareReplay(1)
);
// Make the processing pipeline live at all times.
this.processItemInfo.subscribe();
}
public setItemInfos(itemInfos: IItemInfo[]): Observable<number> {
const myTag = this.itemInfoTag++;
this.setItemInfoSub.next({
payload: itemInfos,
tag: myTag
});
return this.processItemInfo.pipe(
filter(({tag}) => tag == myTag),
map(({payload}) => payload)
);
}
}
RxJS provides the sequenceEqual operator to compare two streams in order. How would one go about testing equality of two streams regardless of order?
Pseudocode:
//how do we implement sequenceEqualUnordered?
from([1,2,3]).pipe(sequenceEqualUnordered(from([3,2,1]))).subscribe((eq) =>
console.log("Eq should be true because both observables contain the same values")
)
In my particular use case I need to wait until a certain set of values has been emitted or error but I don't care what order they're emitted in. I just care that each value of interest is emitted once.
Here's my solution:
import { Observable, OperatorFunction, Subscription } from 'rxjs';
export function sequenceEqualUnordered<T>(compareTo: Observable<T>, comparator?: (a: T, b: T) => number): OperatorFunction<T, boolean> {
return (source: Observable<T>) => new Observable<boolean>(observer => {
const sourceValues: T[] = [];
const destinationValues: T[] = [];
let sourceCompleted = false;
let destinationCompleted = false;
function onComplete() {
if (sourceCompleted && destinationCompleted) {
if (sourceValues.length !== destinationValues.length) {
emit(false);
return;
}
sourceValues.sort(comparator);
destinationValues.sort(comparator);
emit(JSON.stringify(sourceValues) === JSON.stringify(destinationValues));
}
}
function emit(value: boolean) {
observer.next(value);
observer.complete();
}
const subscriptions = new Subscription();
subscriptions.add(source.subscribe({
next: next => sourceValues.push(next),
error: error => observer.error(error),
complete: () => {
sourceCompleted = true;
onComplete();
}
}));
subscriptions.add(compareTo.subscribe({
next: next => destinationValues.push(next),
error: error => observer.error(error),
complete: () => {
destinationCompleted = true;
onComplete();
}
}));
return () => subscriptions.unsubscribe();
});
}
As many of RxJS operators have some input parameters and as all of them return functions, sequenceEqualUnordered also has some input parameter (mostly the same as Rx's sequenceEqual operator) and it returns a function. And this returned function has the Observable<T> as the source type, and has Observable<boolean> as the return type.
Creating a new Observable that will emit boolean values is exactly what you need. You'd basically want to collect all the values from both source and compareTo Observables (and store them to sourceValues and destinationValues arrays). To do this, you need to subscribe to both source and compareTo Observables. But, to be able to handle subscriptions, a subscriptions object has to be created. When creating a new subscriptions to source and compareTo, just add those subscriptions to subscriptions object.
When subscribing to any of them, collect emitted values to appropriate sourceValues or destinationValues arrays in next handlers. Should any errors happen, propagate them to the observer in error handlers. And in complete handlers, set the appropriate sourceCompleted or destinationCompleted flags to indicate which Observable has completed.
Then, in onComplete check if both of them have completed, and if they all are, compare the emitted values and emit appropriate boolean value. If sourceValues and destinationValues arrays don't have the same lengths, they can't equal the same, so emit false. After that, basically sort the arrays and then compare the two.
When emitting, emit both the value and complete notification.
Also, the return value of function passed to the new Observable<boolean> should be the unsubscribe function. Basically, when someone unsubscribes from new Observable<boolean>, it should also unsubscribe from both source and compareTo Observables and this is done by calling () => subscriptions.unsubscribe(). subscriptions.unsubscribe() will unsubscribe from all subscriptions that are added to it.
TBH, I haven't wrote any tests for this operator, so I'm not entirely sure that I have covered all edge cases.
My first idea. Use toArray on both then zip them together finally sort and compare results?
I think I have read 100+ posts on the topic, and I still cannot figure out how to chain two HttpClient calls using rxjs in Angular 6.
Let's say I have a service with that signature:
GeoService {
getState(): Observable<string> {
return this.http.get<string>(stateURL);
}
getCities(state: string): Observable<string[]> {
return this.http.get<string[]>(citiesURL + state);
}
}
I can't for the life of me figure out how to obtain both the state and the corresponding list of cities in my component:
import { Observable } from 'rxjs';
import { map, flatMap, mergeMap, filter, switchMap } from 'rxjs/operators';
...
ngOnInit() {
this.svc.getState().
pipe(map((state) => {
this.state = state;
return this.svc.getCities(state);
}),
mergeMap((cities) => this.cities = cities))
).
subscribe(console.log('done'));
The code above in one of my 20 random attempts at combining pipe/map/mergeMap/subscribe in every way I could think of... a working example would be really really appreciated :)
Thanks!
Edit: None of the "possible duplicate" posts contain an actual example that works
The 21st attempt would have been correct ;-)
this.svc.getState().
pipe(mergeMap((state) => {
this.state = state;
return this.svc.getCities(state);
}),
tap((cities) => this.cities = cities)))
.subscribe(() => console.log('done'));
The chained Observable goes inside mergeMap. You can think of it as:
First, map the incoming notifaction to an Observable, then merge the resulting "inner" Observable into the "outer" Observable
Also, use tap instead of map if you intend to change an outside state.
You were almost there:
this.svc.getState().
pipe(
mergeMap((state) => {
return this.svc.getCities(state).pipe(map(cities => {
return { state: state, cities: cities }
}));
}),
).subscribe(stateAndCities => console.log(stateAndCities));
I advise you to read this article:
https://blog.strongbrew.io/rxjs-best-practices-in-angular/#using-pure-functions
It also explains why you shouldnt interact with global variables in rxjs operators.
You can do something like this
this.svc.getState().pipe(
tap(state=>this.state=state),
switchMap(this.svc.getCities))
.subscribe(cities=>{
//got the cities
})
the map operator is here to transform the emited value, but the tap operator is used to do something without modifying emited value of the observable.
note that switchMap(this.svc.getCities) is equivalent to switchMap(state=>this.svc.getCities(state)
I'm wanting to implement an Observable / Subject with 3 particular attributes
Remember last emitted value and be able to surface it via a getter (BehaviorSubject)
Only emit when value changes
It must have a strong type such that the getter is known to be available by a consumer (aka. BehaviorSubject.getValue())
I'm thinking of just extending BehaviorSubject but want to make sure I'm not introducing any potential gotchas based on my novice understanding.
export class DistinctUntilChangedBehaviorSubject<T, TValue> extends BehaviorSubject<T> {
constructor(
initialValue: T,
private _distinctKeySelector?: (value: T) => TValue,
private _comparer?: _Comparer<TValue, boolean>
) {
super(initialValue);
}
public subscribe() {
// I'm particularly interested in knowing if this has any gotchas.
// Mostly things like creating subscriptions that don't get disposed as expected.
return super.distinctUntilChanged(
this._distinctKeySelector,
this._comparer
).subscribe.apply(this, arguments);
}
}
So 2 questions:
Does this seem like a reasonable approach / are there any gotchas here?
Is there another preferred way of doing this?
I do not know really why, but I tend to prefer composition over extension.
So I would do something along these lines
import {BehaviorSubject} from 'rxjs';
export class BehaviourSubjectAugmented<T> {
bs: BehaviorSubject<T>;
constructor(initialValue: T, private comparer: (p: T, q: T) => boolean) {
this.bs = new BehaviorSubject(initialValue);
}
getValue() {
return this.bs.getValue();
}
asObservable() {
return this.bs.asObservable()
.distinctUntilChanged(this.comparer);
}
complete() {
return this.bs.complete();
}
next(value: T) {
return this.bs.next(value);
}
}
Turns out my original idea causes a call stack exceeded issue. I'm assuming that distinctUntilChanged must call subscribe internally thus causing infinite recursion.
I ended up finding a simpler way to get what I needed by simply adding a method to an ISubject instance.
function distinctUntilChangedBehaviorSubject(
initialValue: number
): ISubject<number> & { getValue(): number } {
const observer = new BehaviorSubject<number>(initialValue);
const observable = observer.distinctUntilChanged();
const subject: ISubject<number> = Subject.create(
observer,
observable
);
return Object.assign(
subject,
{
getValue: () => observer.getValue()
}
);
}
I am building a service which exposes an Observable. In this service I receive external function calls which should trigger a next call on the Observable so that various consumers get the subscribe event. During Observer constructor I can call next and everything works great, but how can I access this outside of the constructor so that external triggers can fire next calls?
private myObservable$: Observable<any>;
During service init I do
this.myObservable$ = new Observable(observer => {
observer.next("initial message");
}
Then in other methods of the same service I want to be able to execute something like
this.myObservable$.observer.next("next message");
The above obviously doesn't work, but how can I accomplish this goal?
I'm assuming I'm missing something basic since there must be a way to emit further messages outside of the Observable's initial constructor
You should create a Subject for that
this.myObservable$ = new Subject();
And then you can call at any point:
this.myObservable$.next(...);
Or use subscribe:
this.myObservable$.subscribe(...)
Actually Subject is used for both publisher and subscriber, and here I think you need only to publish your value, so simply use Observable.
By using observable, assign Subscriber to class level variable and then use it, like below code
subscriber: Subscriber<boolean>;
public observe(): Observable<boolean> {
return new Observable<boolean>(subs => {
this.subscriber = subs;
});
}
public callNext() {
if (this.subscriber) {
this.subscriber.next();
this.subscriber.complete();
}
}
Two ways:
Make myObservable$ public:
public myObservable$: Observable;
Encapsulate the observable in a subject stream, and provide a helper to call next:
export class TestService {
public myObservable$: Observable;
private _myObservableSubject: Subject;
constructor() {
this._myObservableSubject = new Subject();
this.myObservable$ = this._myObservableSubject.asObservable();
}
public NextMessage(message?: string): void {
this._myObservableSubject.next(message);
}
}
Observable: You have to call the next() function from inside the constructor and only one time you can subscribe
message = new Observable((observer)=>{
observer.next(9);
})
this.messsage.subscribe((res)=>{
console.log(res)
})
output: 9
Subject: You have to call next() function from outside the constructor and multiple times you can subscribe.
The subject does not store any initial value before subscribe.
messsage = new Subject()
this.messsage.next(3)
this.messsage.subscribe((res)=>{
console.log(' A '+res)
})
this.messsage.next(4)
this.messsage.next(5)
this.messsage.subscribe((res)=>{
console.log(' B '+res)
})
this.messsage.next(6)
output:
A 4
A 5
A 6
B 6
BehaviorSubject: You have to call next() function from outside the constructor and multiple times you can subscribe.
The BehaviorSubject does store only one initial value before subscribe.
messsage = new BehaviorSubject ()
this.messsage.next(3)
this.messsage.subscribe((res)=>{
console.log(' A '+res)
})
this.messsage.next(4)
this.messsage.next(5)
this.messsage.subscribe((res)=>{
console.log(' B '+res)
})
this.messsage.next(6)
output:
A 3
A 4
A 5
B 5
A 6
B 6
I ended up combining a couple of things:
olsn's answer, which nicely demonstrates Subject's ease of use
Ravi's answer, which correctly points out that we only want an Observable exposed
a more functional approach, because this and Class give me the shivers
TypeScript typings for generic use
const createObservableWithNext = <T>(): {
observable: Observable<T>;
next: (value: T) => void;
} => {
const subject = new Subject<T>();
const observable = subject.asObservable();
const next = (value: T) => subject.next(value);
return {
observable,
next,
};
};