I am making two http requests, each return a observable<IProduct>; and I want to combine both into a local object and use the async pipe to bring values from each.
productA$: observable<IProduct>;
productB$: observable<IProduct>;
combinedProds$: ?
this.productA$ = httpCall();
this.productB$ = httpCall();
this.combinedProds$ = combineLatest([
this.productA$,
this.productB$
])
.pipe(
map(([productA, productB]) =>
({ productA, productB}))
);
This issue I'm getting, I don't know what type combinedProds$ should be.
Maybe forkJoin is the one you are looking for ?
forkJoin work best with Http call and I'm using it a lot when dealing with http request
// RxJS v6.5+
import { ajax } from 'rxjs/ajax';
import { forkJoin } from 'rxjs';
/*
when all observables complete, provide the last
emitted value from each as dictionary
*/
forkJoin(
// as of RxJS 6.5+ we can use a dictionary of sources
{
google: ajax.getJSON('https://api.github.com/users/google'),
microsoft: ajax.getJSON('https://api.github.com/users/microsoft'),
users: ajax.getJSON('https://api.github.com/users')
}
)
// { google: object, microsoft: object, users: array }
.subscribe(console.log);
Update
forkJoin return an Observable<any> so you can change your like this
combinedProds$: Observable<any>
Related
I have a number of components on a page, which all use observables to get API data. I pass these observables to a loading service, which I want to display a loader from when the first observable is passed until the last one has finalised.
Loading service:
private _loading = new BehaviorSubject<boolean>(false);
readonly loading$ = this._loading.asObservable();
showUntilLoadingComplete<T>(observable$: Observable<T>): Observable<T> {
return of(null).pipe(
tap(_ => this._loading.next(true)),
concatMap(_ => observable$),
finalize(() => this._loading.next(false))
);
}
My components then call loading service like so:
this.loadingService.showUntilLoadingComplete(someObservable$)
.subscribe(data=> {
// do stuff
});
However, due to the first observable finalising, the behaviour subject gets passed false and this in turn stops the loader from showing. I have considered creating another behaviour subject to store an array of the active observables, and remove them from here once finalised, and then subscribing to that and setting the loader off once the array has no length. But this doesn't seem like a great solution, so I am looking for others input.
Since you're depending on the same loading$ Observable in a singleton service, then you can add another property to reflect the active number of calls, then turn the loading off only if there is no other active call.
Try something like the following:
private _active: number = 0;
private _loading = new BehaviorSubject<boolean>(false);
readonly loading$ = this._loading.asObservable();
showUntilLoadingComplete<T>(observable$: Observable<T>): Observable<T> {
return of(null).pipe(
tap(() => {
this._loading.next(true);
this._active++;
}),
concatMap((_) => observable$),
finalize(() => {
this._active--;
if (!this._active) {
this._loading.next(false);
}
})
);
}
I have an app where questions are shown to users.
Drafts for the questions are loaded from a SharePoint list. Each draft contains a key which is used to load proper responses to the question from another SharePoint list. Here's how I currently implemented it:
interface QuestionDraft {
title: string;
responseKey: string;
}
interface Question {
title: string;
responses: string[];
}
const drafts: QuestionDraft[] = [];
const questions: Question[] = [];
// stub
private getDrafts(): Observable<QuestionDraft> {
return from(drafts);
}
// stub
private getResponses(key: string): Observable<string> {
return of(key, key, key);
}
main(): void {
getDrafts().subscribe(
data => {
const res: string[] = [];
getResponses(data.responseKey).subscribe(
d => res.push(d),
error => console.error(error),
() => questions.push({
title: data.title,
responses: res
})
);
}, error => console.error(error),
() => console.log(questions)
);
}
This solution works fine, but I think the code in main() looks messy. Is there an easier way to do the same thing, for example using mergeMap or something similar?
You can use mergeMap to map to a new Observable and toArray to collect the emitted values in an array. Use catchError to handle errors in your streams and map to an alternative Observable on errors.
This code will work just like your code with the emitted questions array containing all questions up until getDrafts throws an error and exluding questions for which getResponses threw an error.
getDrafts().pipe(
mergeMap(draft => getResponses(draft.responseKey).pipe(
toArray(),
map(responses => ({ title: draft.title, responses } as Question)),
catchError(error => { console.error(error); return EMPTY; })
)),
catchError(error => { console.error(error); return EMPTY; }),
toArray()
).subscribe(qs => { console.log(qs); questions = qs; })
Keep in mind that the questions in the final array will not necessarily be in the same order as the drafts coming in. The order depends on how fast a getResponses Observable completes for a specific draft. (This is the same behaviour as your current code)
To ensure that the questions will be in the same order as the drafts you can use concatMap instead of mergeMap. But this might slow down the overall execution of the task as the responses for the next draft will only be fetched after the responses for the previous draft completed.
You can try and use flatMap to make it cleaner.
RxJs Observables nested subscriptions?
this.getDrafts()
.flatMap(function(x){return functionReturningObservableOrPromise(x)})
.flatMap(...ad infinitum)
.subscribe(...final processing)
If you use RxJS version 6 you must use pipe() method and turned flatMap into mergeMap.
in rxjs 6, example of #emcee22 will be look:
this.getDrafts()
.pipe(
.mergeMap(function(x){return functionReturningObservableOrPromise(x)}),
.mergeMap(...ad infinitum)
).subscribe(...final processing)
I am a bit confused with rxjs operators. I have few api calls that return observable:
getCurrentUser(): Observable<any> {
return this.http.get<any>(userUrl);
}
tagsList(): Observable<string[]> {
return this.http.get<string[]>(tagsUrl);
}
timezonesList(): Observable<Timezone[]> {
return this.http.get<Timezone[]>(timezonesUrl);
}
I want to call getCurrentUser() then with result of returning value call action LoadUser(user)
Then after user loads call multiple async requests at the same time:
tagsList(), timezonesList()
And then with results of returning value of them call actions LoadTags(tags), LoadTimezones(timezones)
So it should looks like something like this:
init() {
this.accountsApi.getCurrentUser()
.map((user: User) => this.store.dispatch(new LoadUser({ user })))
.map(
this.commonApi.tagsList(),
this.commonApi.timezonesList(),
this.commonApi.authoriztionServicesList()
)
.map((tags, timezones, authorizationServices) => {
this.store.dispatch(new tagsActions.LoadTags(tags));
this.store.dispatch(new timezonesActions.LoadTimezones(timezones));
this.store.dispatch(new authorizationServicesActions.LoadAuthorizationServices(authorizationServices));
});
}
I know that this operators are wrong. What operators should i use for this? I have already done it with promises, but i am sure that i can do it with rxjs operators in less line of code.
P.S. Also it is interesting for me how i can do this with async / await? Thank you
In your original code you are using map a bit too much, for some use cases you may not need to map.
init() {
return this.accountsApi.getCurrentUser()
.do((user: User) => this.store.dispatch(new LoadUser({ user })))
.forkJoin(
this.commonApi.tagsList(),
this.commonApi.timezonesList(),
this.commonApi.authoriztionServicesList()
)
.do((results) => {
this.store.dispatch(new tagsActions.LoadTags(results[0]));
this.store.dispatch(new timezonesActions.LoadTimezones(results[1]));
this.store.dispatch(new authorizationServicesActions.LoadAuthorizationServices(results[2]));
});
}
forkJoin lets you fire off many observable subscriptions and once all subscriptions produce values you get a single array of observable values back.
The do operator introduces side effects to launch your store actions since you don't want to create any arrays.
In a component, in ngOnInit() I've got two subscriptions to a data service. I want to do some processing once both subscriptions have returned. Whats the best way to do this? I can just process at the end of each, this just seems a little inefficient and won't work for which ever subscription activates first,
Thanks,
Component.TS
ngOnInit()
{
this.dataService.dataA().subscribe((dataAJSON) =>
{
this.dataA= dataAJSON
}
this.dataService.dataB().subscribe((dataBJSON) =>
{
this.dataB= dataBJSON
}
DataService
import { Injectable } from '#angular/core';
import { Http } from '#angular/http';
import 'rxjs/add/operator/map';
#Injectable()
export class PMDataService
{
constructor(public http : Http)
{
}
dataA()
{
var dataA: any;
var json;
dataA= this.http.get("./assets/dataa.json")
.map(res => res.json());
return dataA
}
dataB()
{
var dataB: any;
var json;
dataB= this.http.get("./assets/datab.json")
.map(res => res.json());
return dataB
}
}
You can use Observable#forkJoin function on the Observables. It emits the last value from each when all observables complete,
Observable.forkJoin(this.dataService.dataA(), this.dataService.dataB())
.subscribe(val => /* val is an array */)
The method used depends on how you want to receive the data:
You can use the zip function. Emits once when all have emitted once. So similar to Promise.all except not on completion.
Observable.zip(obs1, obs2).subscribe((val) => { ... });
You can use the forkJoin function. Emits once when all have completed. So exactly like Promise.all.
Observable.forkJoin(obs1, obs2).subscribe((val) => { ... });
You can use the merge function. Emits in order of emission so could be 1st then 2nd or 2nd then 1st:
obs1.merge(obs2).subscribe((val) => { ... });
You can use concat function. Emits in order 1st then 2nd regardless if 2nd emits first:
obs1.concat(obs2).subscribe((val) => { ... });
It's best practice to split these up into a couple lines for clarity.
const obs1 = Rx.Observable.of(1,2,3);
const obs2 = Rx.Observable.of(1,2,3);
const example = Observable.zip(obs1, obs2);
//const example = Observable.forkJoin(obs1, obs2);
//const example = obs1.merge(obs2);
//const example = obs1.concat(obs2);
example.subscribe(val => { ... });
You could use the operator Zip or CombineLatest from rxjs.
See ReactiveX operators
You could do something like this:
Observable.zip(
this.http.get("./assets/dataa.json"),
this.http.get("./assets/dataa.json")
.take(1)
.map(values => [values[0].json(), values[1].json()])
.subscribe(values => {
// do something with my values
});
You could You can use concat to combine the observables and return a single observable.
Subscribe to observables in order as previous completes, emit values
changed service code
import 'rxjs/add/operator/concat';
export class PMDataService
{
data(){
return this.dataA().concat(this.dataB());
}
// methods dataA and dataB are unchanged, some of the constructor
}
Component code
ngOnInit(){
this.dataService.data().subscribe((dataJSON) =>
{
this.dataA= dataAJSON[0];
this.dataB= dataAJSON[1];
}
}
The function more() is supposed to return an Observable from a get request
export class Collection {
public more = (): Observable<Response> => {
if (this.hasMore()) {
return this.fetch();
} else {
// return empty observable
}
};
private fetch = (): Observable<Response> => {
return this.http.get("some-url").map((res) => {
return res.json();
});
};
}
In this case I can only do a request if hasMore() is true, else I get an error on subscribe() function subscribe is not defined, how can I return an empty Observable?
this.collection.more().subscribe(
(res) => {
console.log(res);
}, (err) => {
console.log(err);
}
);
With the new syntax of RxJS 5.5+, this becomes as the following:
// RxJS 6
import { EMPTY, empty, of } from "rxjs";
// rxjs 5.5+ (<6)
import { empty } from "rxjs/observable/empty";
import { of } from "rxjs/observable/of";
empty(); // deprecated use EMPTY
EMPTY;
of({});
Just one thing to keep in mind, EMPTY completes the observable, so it won't trigger next in your stream, but only completes. So if you have, for instance, tap, they might not get trigger as you wish (see an example below).
Whereas of({}) creates an Observable and emits next with a value of {} and then it completes the Observable.
E.g.:
EMPTY.pipe(
tap(() => console.warn("i will not reach here, as i am complete"))
).subscribe();
of({}).pipe(
tap(() => console.warn("i will reach here and complete"))
).subscribe();
For typescript you can specify generic param of your empty observable like this:
import 'rxjs/add/observable/empty'
Observable.empty<Response>();
RxJS6 (without compatibility package installed)
There's now an EMPTY constant and an empty function.
import { Observable, empty, EMPTY, of } from 'rxjs';
//This is now deprecated
var delay = empty().pipe(delay(1000));
var delay2 = EMPTY.pipe(delay(1000));
Observable.empty() doesn't exist anymore.
Several ways to create an Empty Observable:
They just differ on how you are going to use it further (what events it will emit after: next, complete or do nothing) e.g.:
Observable.never() - emits no events and never ends.
Observable.empty() - emits only complete.
Observable.of({}) - emits both next and complete (Empty object literal passed as an example).
Use it on your exact needs)
In my case with Angular2 and rxjs, it worked with:
import {EmptyObservable} from 'rxjs/observable/EmptyObservable';
...
return new EmptyObservable();
...
Yes, there is am Empty operator
Rx.Observable.empty();
For typescript, you can use from:
Rx.Observable<Response>.from([])
Since all the answers are outdated, I will post the up to date answer here
In RXJS >= 6
import { EMPTY } from 'rxjs'
return EMPTY;
You can return Observable.of(empty_variable), for example
Observable.of('');
// or
Observable.of({});
// etc
Differents way to return empty observable :
Observable.from({});
Observable.of({});
EMPTY
https://www.learnrxjs.io/learn-rxjs/operators/creation/empty
Or you can try ignoreElements() as well
RxJS 6
you can use also from function like below:
return from<string>([""]);
after import:
import {from} from 'rxjs';
Came here with a similar question, the above didn't work for me in: "rxjs": "^6.0.0", in order to generate an observable that emits no data I needed to do:
import {Observable,empty} from 'rxjs';
class ActivatedRouteStub {
params: Observable<any> = empty();
}
Try this
export class Collection{
public more (): Observable<Response> {
if (this.hasMore()) {
return this.fetch();
}
else{
return this.returnEmpty();
}
}
public returnEmpty(): any {
let subscription = source.subscribe(
function (x) {
console.log('Next: %s', x);
},
function (err) {
console.log('Error: %s', err);
},
function () {
console.log('Completed');
});
}
}
let source = Observable.empty();
You can return the empty observable with all different ways but challenge is to to return it with the expected type -
Here is the way to create a empty observable with type -
intercept(req: HttpRequest<any>, next: HttpHandler): Observable<HttpEvent<any>> {
return next.handle(this.setHeaders(req))
.pipe(
catchError((error: HttpErrorResponse) => {
// you write your logic and return empty response if required
return new Observable<HttpEvent<any>>();
}));
}
there is another: EMPTY const
Replaced with the EMPTY constant or scheduled (e.g. scheduled([], scheduler)). Will be removed in v8. (got this form phpstorm hint)