RxJS multiple switchMap and map operators... is there a better way? - rxjs

I'm pretty new to RxJS and am wondering if I am doing this right... in the ngOnInit() function below, I get a client object, then pipe it...
Is there a better way to do the repeat switchMap/map operations below?
My code works... but I am wondering if there is a more elegant approach that I should be adopting...
public client: Client;
public contract: Contract;
public alreadyPendingContract: boolean;
public alreadyActiveContract: boolean;
public minimumStartDate: Date;
public minimumEndDate: Date;
public rolloverExclusionDate: Date;
public startDateFilter;
ngOnInit() {
this.clientService.getClient$().pipe(
filter(client => client != null),
map(client => this.client = client),
pluck('client_id'),
map((client_id: string) => {
this.clientContractForm.get('client_id').setValue(client_id);
return client_id;
}),
switchMap((client_id: string) => {
return this.contractAddService.getAlreadyPendingContract$(client_id);
}),
map(alreadyPendingContract => {
this.alreadyPendingContract = alreadyPendingContract;
return this.client.client_id;
}),
switchMap((client_id: string) => {
return this.contractAddService.getAlreadyActiveContract$(client_id);
}),
map(alreadyActiveContract => {
this.alreadyActiveContract = alreadyActiveContract;
}),
switchMap(() => {
return this.contractAddService.getMinimumStartDate$(this.client.client_id);
}),
map((minimumStartDate: IMinimumStartDate) => {
this.minimumStartDate = minimumStartDate.minimumStartDate;
this.rolloverExclusionDate = minimumStartDate.rolloverExclusionDate;
this.startDateFilter = (m: Moment): boolean => {
// Filters out the rollover exclusion day from being an available start date.
return !moment.utc(m).isSame(moment.utc(this.rolloverExclusionDate), 'day');
}
})
).subscribe();
}

I am not sure this is more elegant, but it is an alternative way
ngOnInit() {
this.clientService.getClient$().pipe(
filter(client => client != null),
map(client => {
this.client = client;
this.clientContractForm.get('client_id').setValue(client_id);
return client.client_id;
},
switchMap(client_id => this.doStuffWithClientId(client_id)),
map((minimumStartDate: IMinimumStartDate) => {
this.minimumStartDate = minimumStartDate.minimumStartDate;
this.rolloverExclusionDate = minimumStartDate.rolloverExclusionDate;
this.startDateFilter = (m: Moment): boolean => {
// Filters out the rollover exclusion day from being an available start date.
return !moment.utc(m).isSame(moment.utc(this.rolloverExclusionDate), 'day');
}
})
).subscribe();
}
doStuffWithClientId(clientId: string) {
return this.contractAddService.getAlreadyPendingContract$(client_id).pipe(
tap(alreadyPendingContract => this.alreadyPendingContract = alreadyPendingContract),
switchMap(() => this.contractAddService.getAlreadyActiveContract$(clientId)),
tap(alreadyActiveContract => this.alreadyActiveContract = alreadyActiveContract),
switchMap(() => this.contractAddService.getMinimumStartDate$(clientId)),
)
}
I have not tested the code, so there may well be syntax mistakes. The basic idea though is to isolate all the things which depend on client_id into one function which receives the clientId as input and therefore makes it visible throughout the entire function.

Related

Observable subscribe vs pipe - rxjs

I have following function in service to retrieve data:
public getData(): Observable<dataObj> {
const endpointURL = 'xyz';
return response = this.responseHandler
.makeRequest<dataObj>('get', endpointURL)
.pipe(
startWithTap(() => this._requestsInProgress.next(++this.requestsInProgress)),
finalize(() => this._requestsInProgress.next(--this.requestsInProgress))
)
}
in different place I have something like this and I call that function:
export class SearchAndFilterService {
private _data: BehaviorSubject<dataObj> = new BehaviorSubject(null);
private _searchTerm: BehaviorSubject<string> = new BehaviorSubject(null);
private _filterTerms: BehaviorSubject<string[]> = new BehaviorSubject(null);
public readonly data$: Observable<dataObj>;
constructor(
private service: Service
) {
this.data$ = combineLatest([
this._data.asObservable(),
this._searchTerm.asObservable(),
this._filterTerms.asObservable()
]).pipe(
map((tuple) => {
const [data, searchTerm, filterTerms] = tuple;
if (data) {
return {
...data,
array: this.searchAndFilterData(
data?.array,
searchTerm,
filterTerms
)
};
}
return data;
}),
);
}
public updateDonorAppointments() {
this.service.getData().pipe(tap(
(data) => this._data.next(data)
))
}
I thought that pipe should create a subscription to returned observable and this will work. However instead of pipe I must use .subscribe to make it work. Like this:
public updateDonorAppointments() {
this.service.getData().subscribe(
(data) => this._data.next(data)
)
}
Am I missing something? Or is this approach totally wrong?
Thanks

rxjs: subscribing to observable in map

My first observable returns an array of Persons. I want to update each person of that array with a list of clients from second observable. How do I do that? So far I have this:
const json: Person[] = [new Person('Alice'), new Person('Bob')];
const resultsObservable = new Observable<string[]>(subscriber => {
setTimeout(() => {
subscriber.next(['Client1', 'Client2', 'Client3']);
subscriber.complete();
}, 1000);
});
of(json).pipe(
switchMap( dataArray => {
return from(dataArray);
}),
map((x: Person) => {
resultsObservable.subscribe(r => {
x.clients = r;
});
return x;
}),
).subscribe(value => {
console.log(value);
});
}
Person:
export class Person{
name: string;
clients?: string[];
constructor(name: string) {
this.name = name;
}
}
But the problem is that return happens before the values are set, so at the end value of person.clients is undefined. How do I fix this? Or what is a better way to achieve what I'm trying to do?
Ok I think I found what I was looking for:
const result = persons.pipe(
mergeMap(p => resultsObservable.pipe(map(clients => {
p.clients = clients;
return p;
}))),
);
result.subscribe(p => console.log(p));

Passing value from one RxJS operator to another

Here is my code:
#Injectable()
export class TraitementDetailEffects {
ingoing_loadDetail: { traitementID: number, obs: Promise<any> };
#Effect()
loadTraitementDetail$: Observable<Action> = this.actions$.pipe(
ofType(ETraitementDetailActions.loadTraitementDetail),
map((action: LoadTraitementDetail) => action.payload),
switchMap((traitementID) => {
if (this.ingoing_loadDetail && this.ingoing_loadDetail.traitementID === traitementID) {
return this.ingoing_loadDetail.obs;
}
const obs = this.traitementsService.loadDetail(traitementID);
this.ingoing_loadDetail = {traitementID: traitementID, obs: obs};
return obs;
}),
map(result => {
this.ingoing_loadDetail = null;
//here I don't have access to traitementID :'(
return new LoadTraitementDetailSuccess(traitementID, result);
})
);
constructor(
private actions$: Actions,
private traitementsService: TraitementsService
) {
}
}
I'm trying to pass the variable or value traitementID to the last map.
I tried to avoid the last map with an async await but then I get a weird errors "Effect dispatched an invalid action" and "Actions must have a type property" (FYI all my actions have a type property).
Try to bake this id into observable's resolve, like:
switchMap((traitementID) => {
return this.traitementsService.loadDetail(traitementID).pipe(
map(detail => ({detail,traitementID}))
);
}),
map(({detail,traitementID}) => {
...
})

Angular 2 Observable get into an Infinite loop

I'm very new to Angular2!. We have a search method which create an async observable and executing it. When provide a valid input, it works good. But an invalid input is causing an Infinite loop. Please let me know if you see the issue.
Please see the code below.
const ngPromiseToObservable = <T>(p: ng.IHttpPromise<ng.IHttpPromiseCallbackArg<T>>): Observable<ng.IHttpPromiseCallbackArg<T>> => {
const o = new Subject();
p.catch((e) => o.error(e));
p.then((v) => o.next(v));
return o;
};
export class HttpObservable
{
public static $inject = [
"$rootScope",
"$http",
"HttpErrors",
];
constructor(
private $rootScope: ng.IScope,
private $http: ng.IHttpService,
private httpErrors: Subject<Object>
) {}
handleHttpResponseErrors<T>(o: Observable<T>): Observable<T>
{
return o.do(
_.noop,
(e: HttpResponseError) => this.httpErrors.next(e),
_.noop
);
}
applyAsyncOnAction<T>(o: Observable<T>): Observable<T>
{
let applyAsync: () => void = () => {
return this.$rootScope.$applyAsync();
};
return o.do(applyAsync, applyAsync, applyAsync);
}
// tslint:disable-next-line:no-any
post<T>(url: string, data: any, config?: Object): Observable<T>
{
return this.handleHttpResponseErrors(this.applyAsyncOnAction(ngPromiseToObservable(this.$http.post(url, data, config)).map((x) => x.data)));
}
// tslint:disable-next-line:no-any
put<T>(url: string, data: any, config?: Object): Observable<T>
{
return this.handleHttpResponseErrors(this.applyAsyncOnAction(ngPromiseToObservable(this.$http.put(url, data, config)).map((x) => x.data)));
}
get<T>(path: string): Observable<T>
{
return this.handleHttpResponseErrors(this.applyAsyncOnAction(ngPromiseToObservable(this.$http.get(path)).map((x) => x.data)));
}

Observable.bindCallback only returns value if I subscribe to it directly

Observable.bindCallback only returns value if I subscribe to it directly
in other words, this works fine:
return this.store.select(store => store.appDb.appBaseUrl)
.take(1)
.mergeMap(baseUrl => {
const url = `${baseUrl}?command=GetCustomers&resellerUserName=aaa&resellerPassword=bbbb`;
return this.http.get(url)
.map(res => {
var xmlData = res.text()
const boundCallback = Observable.bindCallback(this.processXml, (xmlData: any) => xmlData);
return boundCallback(this, xmlData)
.subscribe((x) => {
return x;
});
})
})
however I need to avoid the subscription as I am running inside #effect which auto subscribes for me, so I run:
return this.store.select(store => store.appDb.appBaseUrl)
.take(1)
.mergeMap(baseUrl => {
const url = `${baseUrl}?command=GetCustomers&resellerUserName=aaa&resellerPassword=aaa`;
return this.http.get(url)
.map(res => {
var xmlData = res.text()
const boundCallback = Observable.bindCallback(this.processXml, (xmlData: any) => xmlData);
var d:any = boundCallback(this, xmlData)
return d;
}).map(d=>{console.log(d)})
})
but instead of getting a value now I am getting a:
and this is d:
regards
Sean
If I understand what you want to do it should look something like this (obviously I didn't test it):
return this.store.select(store => store.appDb.appBaseUrl)
.take(1)
.mergeMap(baseUrl => {
const url = `${baseUrl}?command=GetCustomers&resellerUserName=aaa&resellerPassword=aaa`;
return this.http.get(url)
.mergeMap(res => {
var xmlData = res.text()
const boundCallback = Observable.bindCallback(this.processXml, (xmlData: any) => xmlData);
return boundCallback(this, xmlData)
}).do(d => console.log(d))
})
I used mergeMap() because I want to get the value from the Observable returned by boundCallback().
Also when using map() you always need to return a value that is propagated further. In your example you're not returning anything so you can use just do() to see print what values go through.
Edit:
So this is a simplified version of what you're trying to do.
class A {
private processXml(context, xmlData, cb) {
context.parseString(xmlData, {attrkey: 'attr'}, function (err, result) {
if (err || !result) return cb(null); return cb(result);
})
}
private parseString(str, _, cb) {
return cb(null, str);
}
private mockHttpGet() {
return Rx.Observable.of({
text: () => {
return 'abc';
}
});
}
test() {
return this.mockHttpGet()
.mergeMap(res => {
var xmlData = res.text();
const boundCallback = Rx.Observable.bindCallback(this.processXml, (xmlData: any) => xmlData);
return boundCallback(this, xmlData)
}).do(d => console.log(d))
}
}
let a = new A();
a.test().subscribe(val => console.log('subscribed result', val));
See live demo: https://jsbin.com/reweraw/2/edit?js,console
This demo prints:
abc
subscribe abc
The BoundCallbackObservable (and this applies to operators as well) do nothing until you subscribe to them. That's why in the debugger you see just raw data.
My demo works as you probably want so check out how am I using mergeMap() to get the actual value from the nested Observable and try to replicate the same logic in you application.
found the solution, I was one Obs too deep:
return this.store.select(store => store.appDb.appBaseUrl)
.take(1)
.mergeMap(baseUrl => {
const url = `${baseUrl}?command=GetCustomers&resellerUserName=reseller#ms.com&resellerPassword=XXXX`;
return this.http.get(url)
.map(res => {
return res.text()
}).flatMap(jData=>{
const boundCallback = Observable.bindCallback(this.processXml, (xmlData: any) => xmlData);
return boundCallback(this, jData)
}).map(businessData=>{
return businessData;
})
})

Resources