Chaining observables Angular 5 / Rxjs - javascript

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.

Related

How to ensure observables from different components are complete?

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

Angular and RxJs combine two http requests

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>

RxJS: Mapping object keys to observables

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)

DebounceTime emits all the events which was captred during the time

I need to write the async validator for the reactive form type in angular.
I have implemented it trough promise. But the issue is the validator triggers for each keystroke it strike the server for every keystroke.For implementing the debounce i have implemented the setTimeout for the promise but the issue i faced is it triggers for after the certain millisecon i have defined.
Finally I have implemented the Observable inside the promise to achive all debounceTime, But the issue i faced here is the debounceTime emits all the events.
For example: If I type 'Prem' from input field the following code triggers the server for four time as timeout works.
If any issue in implemetation of the async validator please clarify me.
//Latest code
static hasDuplicateEmail(formControl: FormControl) {
return new Promise((resolve, reject) => {
return new Observable(observer =>
observer.next(formControl.value)).pipe(
debounceTime(600),
distinctUntilChanged(),
switchMap((value) => {
//server side
return MotUtil.fetch('checkForRegisterEmail', {e: formControl.value});
})
).subscribe((res) => {
return (JSONUtil.isEmpty(res)) ? resolve(null) : resolve({duplicate: true});
});
});
}
The debounceTime should work as mentioned in the Docs.
You are trying to approach it in a difficult way. Validator takes argument - AbstractControl. AbstractControl has property - valueChanges which return stream of changes in your formControl. So here you add debouceTime and later do other operations and finaly return this stream back to FormControl:
hasDuplicateEmail(control: AbstractControl) {
return control.valueChanges.pipe(
debounceTime(600),
switchMap(e =>
this.http.get('checkForRegisterEmail', {e}).pipe(
map((res: any) => JSONUtil.isEmpty(res) ? null : { duplicate: true })
)
)
)
}
As you notice I use HttpClient as it is the way you make HTTP calls in Angular (it is designed to work on streams rather then Promises)
emailValidator(/** params that you need inside switchMap */): AsyncValidatorFn {
return (control: AbstractControl): Observable<ValidationErrors | null> => {
return of(control.value).pipe(
delay(500), //for me works with delay, debounceTime are not working with asyncValidator
filter(email=> !!email), //length or another logic there is not emails with less than 10 characters
distinctUntilChanged(),
switchMap(/** as you need */),
map(exist => (exist ? {duplicate: true} : null)),
catchError(() => of(null))
);
};
}

Angular 5 switch to HttpClient broke my services

Before Angular 5, we imported and used Http from #angular/http. Now we use HttpClient from #angular/common/http. There's some level of convenience added, because no we don't need the unnecessary step of turning all of our response data to JSON format every time we make a call using .map(res => res.json()). However, now we aren't suppose to use .map. We are suppose to go straight to .subscribe(). Again, this is suppose to be convenient. However, I used to do some logic in my services, where my API calls live, before returning the data to the subscribing functions in other components. For example,
The component:
getData(id) {
this.service.getDataFromApi(id).subscribe(res => this.doSomething(res));
}
The service:
public getDataFromApi(id) {
if (this.data.length > 0) {
return Observable.of(this.data);
} else {
return this.http.get(this.apiPath + id)
.map((response: any) => {
this.data = response.json();
return this.data;
});
}
}
Now, they suggest we shorten the service call to something like the following:
public getDataFromApi(id) {
if (this.data.length > 0) {
return Observable.of(this.data);
} else {
return this.http.get(this.apiPath + id);
}
}
I am not suppose to use .map because it will no longer be supported in the future. How can I do some logic before returning the data? I don't want to make a call for some data that already exists and doesn't change. Multiple components are using this service. Am I suppose to turn every single service call into a raw promise and run some logic before resolving? If I were to just call subscribe right on the component's function, I wouldn't even need to have the service. Am I missing something here?
You can use map. The new HttpClient has the added convenience of defaulting the response type to JSON, but it still returns an Observable -- and there are no plans to deprecate map from the Observable API.
So, your code only needs slight modification (take out the .json()):
public getDataFromApi(id) {
if (this.data.length > 0) {
return Observable.of(this.data);
} else {
return this.http.get(this.apiPath + id)
.map((response: any) => {
this.data = response;
return this.data;
});
}
}
See the Observable API docs here.
If you use the new HttpClient, there is no res.json method. It will work automatically, just pass the response type like this:
return this.http.get(this.apiPath + id)
.toPromise()
.then(data => {
console.log(data)
return data
});

Categories