Extract a value from an Observable in RxJs - rxjs

I know I can subscribe to an object of type Observable if I need to extract a value from it, but what if I want to use operators from RxJs to reach the same goal. Please see the following code
this.placeService.getPlace(paramMap.get('placeId')).pipe(
take(1),
tap(place => { console.log('Place: ' + place); this.place = place; }));
What is wrong? Why it does not work as expected (this.place is equal to undefined)?

You have to subscribe to observable.
this.placeService.getPlace(paramMap.get('placeId'))
.pipe(
take(1),
tap(place => { console.log('Place: ' + place); this.place = place;}))
.subscribe();
the same use without tap
this.placeService.getPlace(paramMap.get('placeId'))
.pipe(take(1))
.subscribe(place => { console.log('Place: ' + place); this.place = place;});

Related

How to map an Observable with values from another observable

I obtain my Ofertas here
getOfertasByYear(year:number): Observable<Oferta[]> {
return this.http.get<Oferta[]>(`${this.urlWebAPI}/ofertas/year/${year}`)
.pipe(
map(ofertas=>
ofertas.map(oferta=>({
...oferta,
añoPresentada:new Date(oferta.fechaPresentacionFulcrum).getFullYear(),
organismoId:¿¿¿???
}) as Oferta)
),
tap(data => console.log('OfertasService-getOfertasByYear(): ', data)
),
catchError(this.handleError)
)
}
But I need to calculate his organismoId and that is here
getOrganismoDeOferta(ofertaId:string): Observable<Organismo> {
return this.http.get<Organismo>(`${this.urlWebAPI}/organismos/oferta/${ofertaId}`)
.pipe(
tap(//data=>console.log('OfertasService-getOrganismos(): ', data)
),
catchError(this.handleError)
)
}
And I don't know how to pass the result of this Observable to te mapped property
getOfertasByYear(year:number): Observable<Oferta[]> {
return this.http.get<Oferta[]>(`${this.urlWebAPI}/ofertas/year/${year}`)
.pipe(
map(ofertas=>
ofertas.map(oferta=>({
...oferta,
añoPresentada:new Date(oferta.fechaPresentacionFulcrum).getFullYear(),
organismoId:this.getOrganismoDeOferta(oferta.id).subscribe(data=>{
¿¿¿¿??????
})
}) as Oferta)
),
tap(data => console.log('OfertasService-getOfertasByYear(): ', data)
),
catchError(this.handleError)
)
}
I subscribe to it but I don't know how to make the assignment
I have tried to obtain all Ofertas and All Concursos but neither
ofertas$ = this.dataService.getOfertas();
concursos$ = this.dataService.getConcursos();
ofertasConOrganismos$ = forkJoin([
this.ofertas$,
this.concursos$
])
.pipe(
map(([ofertas, concursos]) =>
ofertas.map(oferta => ({
...oferta,
organismoId: concursos.find(c => c.id == oferta.concursoId).organismoId
}) as Oferta))
);
But I get this error:
Cannot read properties of undefined (reading 'organismoId')
Any idea, please?
Thanks
Instead of using a plain map and then calling .subscribe(), you can use a "Higher-Order Mapping Operator" to handle the inner subscription for you. In this case, let's use switchMap.
The idea is to return an observable inside switchMap that emits the data you need. Since you need to make multiple calls, we can leverage some help from forkJoin.
With forkJoin you pass in an array of observables, and it will emit an array of the results. So here below we map the array of Oferta to an array of observables that will each emit the Oferta with the organismoId appended:
getOfertasByYear(year: number): Observable<Oferta[]> {
return this.http.get<Oferta[]>(`${this.urlWebAPI}/ofertas/year/${year}`).pipe(
switchMap(ofertas => forkJoin(
ofertas.map(oferta => this.appendOrganismo(oferta))
)),
catchError(this.handleError)
)
}
Nothing too fancy for the definition of appendOrganismo(); we just make the http call, then map the result to the desired shape:
private appendOrganismo(oferta: Oferta) {
return this.getOrganismoDeOferta(oferta.id).pipe(
map(organismo => ({
...oferta,
añoPresentada: new Date(oferta.fechaPresentacionFulcrum).getFullYear(),
organismoId: organismo.id
}))
);
}

How can you execute and array of Observables API calls where each API calls waits for the previous?

What I have been playing with is to use combineLatest with concatAll() but they are still being called simultaneously. I could just loop and call each but I am always wondering if there is a better way within the RXJS workflow.
combineLatest(arrayOfApiObservables).pipe(concatAll()).subscribe();
The problem here is that you use the combineLatest operator, which will emit value only after all observables had emitted (e.g. it is calling everything simultaneously).
After that the concatAll can't affect the arrayOfApiObservables because they have alredy been called.
The right aproach is to create a higher-order observable (observable that emits observables), which can be achived with the help of the operator from and after that you can concatAll them to achive the desired result.
concatAll definition as seen in the docs: Converts a higher-order Observable into a first-order Observable by concatenating the inner Observables in order..
let {
interval,
from
} = rxjs
let {
take,
concatAll,
mapTo
} = rxjs.operators
let ref = document.querySelector('#container')
const obs1$ = interval(1000).pipe(take(1), mapTo('obs1'));
const obs2$ = interval(500).pipe(take(1), mapTo('obs2'));
const obs3$ = interval(2000).pipe(take(1), mapTo('obs3'));
let allObservables$ = from([obs1$, obs2$, obs3$])
allObservables$.pipe(
concatAll()
).subscribe((x) => {
console.log(x)
container.innerHTML += `<div>${x}</div>`
})
<script src="https://cdnjs.cloudflare.com/ajax/libs/rxjs/6.5.5/rxjs.umd.js"></script>
<div id="container"></div>
If you want them to be executed in a sequential manner, you could use concatMap() inside pipe().
Something like this:
of(1)
.pipe(
concatMap(result => {
console.log(result);
return of(2);
}),
concatMap(result => {
console.log(result);
return of(3);
}),
concatMap(result => {
console.log(result);
return of(4);
}),
concatMap(result => {
console.log(result);
return of(5);
}),
concatMap(result => {
console.log(result);
return of(6);
})
)
.subscribe(res => {
console.log("finish");
});
But, if you want to execute them all at once and then await for them until they are all completed, then just use forkJoin().

how to unsubscribe a RXJS subscription inside the subscribe method?

I have some javascript:
this.mySubscription = someObservable.subscribe((obs: any) => {
this.mySubscription.unsubscribe();
this.mySubscription = undefined;
}
on execution, the console logs the error ERROR TypeError: Cannot read property 'unsubscribe' of undefined.
I wonder why I can not unsubscribe inside the subscribe lambda function. Is there a correct way to do so? I have read a bit about using dummy-subjects and completing them or using takeUntil/takeWhile and other pipe operators workArounds.
What is a correct way/workaround to unsubscribe a subscription inside the subscription's subscribe-function?
I am currently using a dummy subscription like so:
mySubscription: BehaviorSubject<any> = new BehaviorSubject<any>(undefined);
// when I do the subscription:
dummySubscription: BehaviorSubject<any> = new BehaviourSubject<any>(this.mySubscription.getValue());
this.mySubscription = someObservable.subscribe((obs: any) => {
// any work...
dummySubscription.next(obs);
dummySubscription.complete();
dummySubscription = undefined;
}, error => {
dummySubscription.error(error);
});
dummySubscription.subscribe((obs: any) => {
// here the actual work to do when mySubscription emits a value, before it should have been unsubscribed upon
}, err => {
// if errors need be
});
You shouldn't try to unsubscribe in the subscribe function.
You can unsubscribe with operators like take, takeWhile or takeUntil.
take
Use take(n) to unsubscribe after someObservable emits n times.
someObservable.pipe(
take(1)
).subscribe(value => console.log(value));
takeWhile
Use takeWhile to unsubscribe when an emitted value fails a condition.
someObservable.pipe(
takeWhile(value => valueIsSave(value))
).subscribe(value => console.log(value));
valueIsSave(value): boolean {
// return true if the subscription should continue
// return false if you want to unsubscribe on that value
}
takeUntil
Use takeUntil(obs$) to unsubscribe when the observable obs$ emits.
const terminate = new Subject();
someObservable.pipe(
takeUntil(terminate)
).subscribe(value => console.log(value));
unsub() {
terminate.next() // trigger unsubscribe
}
If you make your stream asynchronous, what you're doing should work. For example, this will not work:
const sub = from([1,2,3,4,5,6,7,8,9,10]).subscribe(val => {
console.log(val);
if(val > 5) sub.unsubscribe();
});
but this will work:
const sub2 = from([1,2,3,4,5,6,7,8,9,10]).pipe(
delay(0)
).subscribe(val => {
console.log(val);
if(val > 5) sub2.unsubscribe();
});
Because the JS event loop is fairly predictable (blocks of code are always run to completion), If any part of your stream is asynchronous, then you can be sure that your subscription will be defined before your lambda callback is invoked.
Should you do this?
Probably not. If your code relies on the internal (otherwise hidden) machinations of your language/compiler/interpreter/etc, you've created brittle code and/or code that is hard to maintain. The next developer looking at my code is going to be confused as to why there's a delay(0) - that looks like it shouldn't do anything.
Notice that in subscribe(), your lambda has access to its closure as well as the current stream variable. The takeWhile() operator has access to the same closure and the same stream variables.
from([1,2,3,4,5,6,7,8,9,10]).pipe(
takeWhile(val => {
// add custom logic
return val <= 5;
})
).subscribe(val => {
console.log(val);
});
takeWhile() can to anything that sub = subscribe(... sub.unsubscibe() ... ), and has the added benefit of not requiring you to manage a subscription object and being easier to read/maintain.
Inspired by another answer here and especially this article, https://medium.com/#benlesh/rxjs-dont-unsubscribe-6753ed4fda87, I'd like to suggest takeUntil() with following example:
...
let stop$: Subject<any> = new Subject<any>(); // This is the one which will stop the observable ( unsubscribe a like mechanism )
obs$
.pipe(
takeUntil(stop$)
)
.subscribe(res => {
if ( res.something === true ) {
// This next to lines will cause the subscribe to stop
stop$.next();
stop$.complete();
}
});
...
And I'd like to quote sentence RxJS: Don’t Unsubscribe from those article title mentioned above :).

RxJS: forkJoin mergeMap

I'm trying to make multiple http requests and get returned data in one object.
const pagesToFetch = [2,3]
const request$ = forkJoin(
from(pagesToFetch)
.pipe(
mergeMap(page => this.mockRemoteData(page)),
)
)
mockRemoteData() return a simple Promise.
After first Observable emits (the once created from first entry of pagesToFetch the request$ is completed, second value in not included. How can I fix this?
You can turn each value in pagesToFetch into an Observable and then wait until all of them complete:
const observables = pagesToFetch.map(page => this.mockRemoteData(page));
forkJoin(observables)
.subscribe(...);
Or in case it's not that simple and you need pagesToFetch to be an Observable to collect urls first you could use for example this:
from(pagesToFetch)
.pipe(
toArray(),
mergeMap(pages => {
const observables = pages.map(page => this.mockRemoteData(page));
return forkJoin(observables);
}),
)
.subscribe(...);
Try the below sample format...
Observable.forkJoin(
URL 1,
URL 2
).subscribe((responses) => {
console.log(responses[0]);
console.log(responses[1]);
},
error => {console.log(error)}
);

Ignore switchMap return value

I want to resolve an observable but I don't want the return value to replace the previous value in the pipe. Is there any asynchronous tap()? I need an operator like a switchMap but I want to ignore the return.
of(1).pipe(switchMap(() => of(2))).subscribe(console.log); // expected: 1
I could create a custom operator but sure there's something built-in in rxjs.
I ended up with this custom operator. It is like tap but resolves observables (and should be updated to also support promises).
export function switchTap<T, R>(next: (x: T) => Observable<R>): MonoTypeOperatorFunction<T>;
export function switchTap<R>(observable: Observable<R>): MonoTypeOperatorFunction<R>;
export function switchTap<T, R>(
arg: Observable<T> | ((x: T) => Observable<R>)
): MonoTypeOperatorFunction<T> {
const next: (x: any) => Observable<T | R> =
typeof arg === 'function' ? arg : (x: any): Observable<T> => arg;
return switchMap<T, T>(value => next(value).pipe(ignoreElements(), concat(of(value))));
}
Usage:
of(1).pipe(switchTap(of(2))).subscribe(console.log) // 1
or with a function:
of(1)
.pipe(
switchTap(value => {
console.log(value); // value: 1
return of(value + 1);
})
)
.subscribe(console.log); // 1
If you just want to simply ignore the values of the subscribe, then just don't pass in any arguments in the subscribe callback:
of(1).pipe(switchMap(() => of(2))).subscribe(()=>{
console.log('no arguments')
});
If you however want to retain the values of the first observable, things can get tricky. One way is to use Subject to retain the value:
//create a BehaviorSubject
var cache = new BehaviorSubject<any>(0);
of(1).pipe(switchMap((first) => {
cache.next(first);
return of(2);
})).subscribe(() => {
console.log(cache.value) //gives 1
});
Or you can use .map() to alter the values. This is kind of hacky and the code is harder to maintain:
of(1).pipe(switchMap((first) => {
return of(2).map(() => first);
})).subscribe((second) => {
console.log(second) //gives 1 because the values was mapped
});
I do it like so
of(2).pipe(
switchMap( num => this.doSmtg(num), num => num)
).subscribe(num => console.log(num)); // 2
Second param of switchmap receives two value the one passed to this.doSmtg and the value returned by doSmtg(num)'s observable.
For anyone new having the same problem I would advise using the resultSelector parameter supported by switchMap and other RxJS mapping operators.
Example:
switchMap(1 => of(2), (one, two) => one)
For further reading: https://www.learnrxjs.io/operators/transformation/mergemap.html
I think you could use delayWhen operator to achieve a similar functionality.
of(1)
.pipe(
delayWhen(value => {
console.log(value); // value: 1
return of(value + 1);
})
).subscribe(console.log); // 1

Resources