Angular - detecting when two subscriptions have updated - javascript

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];
}
}

Related

Rxjs from() operator doesn't send data

I have a simple app on Angular/rxjs/Ngrx which requests list of default films from the api.
component.ts
export class MoviesComponent implements OnInit {
private movies$: Observable<{}> =
this.store.select(fromRoot.getMoviesState);
private films = [];
constructor(public store: Store<fromRoot.State>) {}
ngOnInit() {
this.store.dispatch(new MoviesApi.RequestMovies());
this.movies$.subscribe(film => this.films.push(film));
console.log(this.films)
}
effects.ts
#Effect()
requestMovies$: Observable<MoviesApi.MoviesApiAction> = this.actions$
.pipe(
ofType(MoviesApi.REQUEST_MOVIES),
switchMap(actions => this.MoviesApiServ.getDefaultMoviesList()
.pipe(
mergeMap(movies => of(new MoviesApi.RecieveMovies(movies))),
catchError(err => {
console.log('err', err);
return of(new MoviesApi.RequestFailed(err));
})
)
)
);
service.ts
export class MoviesApiService {
private moviesList = [];
constructor(private http: HttpClient) { }
public getDefaultMoviesList(): Observable<{}> {
DEFAULT_MOVIES.map(movie => this.getMovieByTitle(movie).subscribe(item => this.moviesList.push(item)));
return from(this.moviesList);
}
public getMovieByTitle(movieTitle: string): Observable<{}> {
return this.http.get<{}>(this.buildRequestUrl(movieTitle))
.pipe(retry(3),
catchError(this.handleError)
);
}
}
DEFAULT_MOVIES is just array with titles.
So my getDefaultMoviesList method is not sending data. But if I replace this.moviesList to hardcoced array of values it works as expected.
What I'm doing wrong?
UPD
I wanted to loop over the default list of films, then call for each film getMovieByTitle and collect them in array and send as Observable. Is there any better solution?
1) You should probably move this line to the service contructor, otherwise you will push a second array of default movies every time you getDefaultMoviesList:
DEFAULT_MOVIES.map(movie => this.getMovieByTitle(movie).subscribe(item => this.moviesList.push(item)));
2) Actually you should probably merge the output of each http.get:
public getDefaultMoviesList(): Observable<{}> {
return merge(DEFAULT_MOVIES.map(movie => this.http.get<{}>(this.buildRequestUrl(movieTitle))
.pipe(retry(3),
catchError(this.handleError)
)))
}
3) You should actually only do that once and store it in BehaviorSubject not to make new HTTP request on each getDefaultMoviesList
private movies$: BehaviorSubject<any> = new BehaviorSubject<any>();
public getMovies$() {
return this.movies$.mergeMap(movies => {
if (movies) return of(movies);
return merge(DEFAULT_MOVIES.map(movie => this.http.get<{}>(this.buildRequestUrl(movieTitle))
.pipe(retry(3),
catchError(this.handleError)
)))
})
}
4) Your implementation shouldn't work at all since:
public getDefaultMoviesList(): Observable<{}> {
DEFAULT_MOVIES.map(movie => this.getMovieByTitle(movie).subscribe(item =>
this.moviesList.push(item))); // This line will happen after http get completes
return from(this.moviesList); // This line will happen BEFORE the line above
}
So you will always return an Observable of empty array.
5) You shouldn't use map if you don't want to map your array to another one. You should use forEach instead.
map is used like this:
const mappedArray = toMapArray.map(element => someFunction(element));
You can try creating the observable using of operator.
Ex: of(this.moviesList);
One intersting fact to note is that Observable.of([]) will be an empty array when you subscribe to it. Where as when you subscribe to Observable.from([]) you wont get any value.
Observable.of, is a static method on Observable. It creates an Observable for you, that emits value(s) that you specify as argument(s) immediately one after the other, and then emits a complete notification.

Joining a returned (promise-esq) Observable and a global observable

In my web app's client code I have a class responsible for a bunch of websocket IO. This class has a global itemUpdatedObservable that various parts of the UI can subscribe to to do little things. There is also a public function UpdateItem which returns a promise-esq Observable. When the item is updated in response to the call to UpdateItem I want both the returned observable and global observable to emit. The returned observable should also complete after emitting.
I have come up with this solution:
// Singleton
class API {
readonly itemUpdatedObservable: Observable<Item>;
private pendingItemUpdates: { [id: string]: Observer<Item> };
constructor() {
this.itemUpdatedObservable = new Observable(observer => {
socketio.on('itemUpdated', res => {
// do a bunch of validation on item
// ...
if (!res.error) {
observer.next(res.item);
} else {
observer.error(res.error);
}
let pendingObs = pendingItemUpdates[res.id]
if (pendingObs) {
if (!res.error) {
pendingObs.next(res.item);
} else {
pendingObs.error(res.error);
}
pendingObs.complete()
delete pendingItemUpdates[res.id];
}
})
});
this.pendingItemUpdates
}
public UpdateItem(item: Item): Observable<Item> {
const o = new Observable(observer => {
let id = uniqueId(); // Some helper somewhere.
this.pendingItemUpdates[id] = observer;
socketio.emit('updateitem', {item: item, id: id});
}).publish();
o.connect();
return o;
}
}
My question is if there is a cleaner, shorter way of doing this? I have something like 10+ observables in addition to itemUpdatedObservable that all are events for different Object types. This code is messy and unwieldy especially when I am writing it 10x over. Is there a way to streamline the two observables such that I am only calling observable.next(...) or observable.error(...) once?
The above code blob is a simplification of my actual code, there is a lot more validation and context-specific values and parameters in reality.
Maybe you can start with creating some reusable socket function which return observable.
const socketOn = (event) => {
return Observable.create(obs => {
socketio.on(event, res => {
if (!res.error) {
obs.next(res.item);
} else {
obs.error(res.error);
}
})
}).share()
}
// usuage
itemUpdated$=socketOn('itemUpdated')
itemUpdated$.map(res=>...).catch(e=>...)

Multiple actions with dependencies in Redux-Observable

I am using Redux-Observable Epic in a React & Redux project. I have multiple actions need to emit, originally this is what I have been doing,
1) Catch the START_ACTION in the Epic
2) Fetch remote data
3) Return a new Observanble
for example:
import fetch from 'isomorphic-fetch';
import {Observable} from 'rxjs/Observable';
import {switchMap} from 'rxjs/operator/switchMap';
import {mergeMap} from 'rxjs/operator/mergeMap';
const fetchAsync = (arg) => {
// return an observable of promise
return Observable::from(fetch(url + arg));
}
export function myEpic = (action$) => {
action$.ofType(START_ACTION)
::switchMap(action => {
return fetchAsync('/action.payload')
::mergeMap(result => {
return Observable::of({type: ACTION_COMPLETE, payload: result})
})
})
};
Now what if I have another action SECOND_ACTION need to be emitted after the START_ACTION and before the ACTION_COMPLETE ? In other word, without making sure the SECOND_ACTION hits the reducer, the ACTION_COMPLETE should not be emitted.
I could write another separate Epic function to do this, but is there any other easier way?
To simplify the question, I just want to emit the SECOND_ACTION before the async.
To emit another action before performing the fetchAsync, you can either use Observable.concat or the startWith operator, which is basically sugar for the concat.
export function myEpic = (action$) => {
action$.ofType(START_ACTION)
::switchMap(action => {
return fetchAsync('/action.payload')
::map(result => ({ type: ACTION_COMPLETE, payload: result })
::startWith({ type: SECOND_ACTION })
})
};
Since SECOND_ACTION synchronously follows START_ACTION, keep in mind that often you should just have your reducers listen for START_ACTION instead of emitting another action. Multiple reducers transitions state from the same action is normal and one of the primary benefits of redux.
That said, there are certainly some times where the separation of concerns is more ideal, so this is more of a general tip.
Previous answer
If you want to emit two actions sequentially you can pass the additional actions as arguments to Observable.of(...actions) since it accepts any number of arguments and emits them sequentially.
export function myEpic = (action$) => {
action$.ofType(START_ACTION)
::switchMap(action => {
return fetchAsync('/action.payload')
::mergeMap(result => {
return Observable::of(
{ type: SECOND_ACTION },
{ type: ACTION_COMPLETE, payload: result }
)
})
})
};
If this isn't what you meant, I apologize. The question isn't clear.

Angular RxJS: Do something when an observable is subscribed or unsubscribed [duplicate]

This question already has answers here:
Observable.onSubscribe equivalent in RxJs
(3 answers)
Closed 5 years ago.
I'm translating some Javascript events to Observables using RxJS in an Angular app (Ionic framework). I want to start listening to the events when the "client" calls .subscribe(), and stop listening for them when he calls .unsubscribe(). I've looked at the documentation of the Observable class, but I wasn't able to see how to do that.
So, assuming there were "onSubscribe" and "onUnsubscribe" methods, I'd like to do something like this:
import { Observable, Subject } from 'rxjs';
...
on(name: string) : Observable<Event> {
let observable = new Subject<any>();
observable.onSubscribe(() => {
this.addEventListener(name, (event) => {
observable.next(event);
});
});
observable.onUnsubscribe(() => {
this.removeEventListener(name);
});
return observable;
}
Is there any way to achieve this?
Thanks!
import { Observable, Subject } from 'rxjs';
...
const unsubscribe;
on(name: string) : Observable<Event> {
let observable = new Subject<any>();
// subscribe method returns unsubscribe function, so you have to save in some varibale then call it.
this.unsubscribe = observable.subscribe(() => {
this.addEventListener(name, (event) => {
observable.next(event);
});
});
return observable;
}
// some where else you can do this
unsubscribe();

Return an empty Observable

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)

Categories