Reputation: 2475
I am working on a angular 4.4.4 application, in which merchants can give away points to customer; for that I an giving httpclient srevice call (for this I am using cancateMap
although not sure what to use).
I have multiple merchants and all can give points to user so I want to call httpClient req sequentially for each merchant(one after another).In response I want to show points in view increases like counter and loading bar while points being added. e.g. **
if starbucks add 20 points then in response view should show points incrementing till 20 and determinant loader will be shown till it completes 100%. I want to show this for each and every request
For loader I am using materials determinant loader **: Below is my Component code
import { Component, OnInit } from '@angular/core';
import { Router } from '@angular/router';
import { SharedService } from '../services/shared.service';
import { CommonService } from '../services/common.service';
import { Observable } from 'rxjs/Rx';
@Component({
selector: 'app-dashboard',
templateUrl: './onboarding.component.html',
styleUrls: ['./onboarding.component.scss']
})
export class DashboardComponent implements OnInit {
public merchantsList: Object[];
public points: number = 0;
public counter: number = 0;
public merchantName: string;
public percentage: number;
constructor(private router: Router, private commonService: CommonService, private sharedService: SharedService) { }
ngOnInit() {
this.merchantsList = this.sharedService.getMerchants();
this.transferPoints();
}
transferPoints() {
let data = {
"points": 20,
"issueToPartyName": "O=User1,L=New York,C=US",
"issuerBankPartyRef": "1"
};
Observable.of(...this.merchantsList)
.concatMap((merchant: Object) => {
return this.commonService.getAllMerchatPoints(merchant, data).map((res) => {
return res
});
}).subscribe((res) => {
let timer = Observable.interval(200).subscribe(() => {
this.merchantName = res.initiator;
this.points++;
this.counter++;
this.percentage = (this.counter / 20) * 100;
if (this.percentage === 100) {
timer.unsubscribe();
this.percentage = 0;
this.counter = 0;
}
});
});
}
}
transferPoints() is the method I am calling to give sequential http call using concateMap then in its subscribe I am getting points transferred and merchants name:
HTML is :
<div class="mobile-img">
<div class="home-card flex-container flex-col-direction flex-center-center">
<div>
<img class="icon" src="assets/images/user-big.png" />
</div>
<div>
<div class="amount">{{points}}</div>
<div class="subtext">Points</div>
</div>
<div class="state">Fetching your reward points...</div>
<div class="vendor">{{merchantName|| 'Uber'}}</div>
<mat-progress-bar mode="determinate" value="{{percentage}}"></mat-progress-bar>
</div>
</div>
and service code is :
import { Injectable } from '@angular/core';
import { HttpClient, HttpHandler, HttpHeaders } from '@angular/common/http';
import { Observable } from 'rxjs/Observable';
import 'rxjs/add/operator/map';
import { LoaderService } from './loader.service';
interface NodeResponse {
name: string;
nodeName: string;
nodeType: string;
port: number;
initiator?:string;
}
@Injectable()
export class CommonService extends HttpClient {
private contextPath = "url";
constructor(private http: HttpClient, public httpHandler: HttpHandler, public loaderService: LoaderService) {
}
getAllNodes(): Observable<NodeResponse[]> {
return this.http.get<NodeResponse[]>(`${this.contextPath}:10007/api/transferpoints/getAllNodesInfo`);
}
getAllMerchatPoints(merchant, data): Observable<NodeResponse> {
return this.http.put<NodeResponse>(`${this.contextPath}:${merchant.port}/api/transferpoints/issueandtransfer`, data);
}
}
getAllMerchatPoints is the service that I am calling
Problem is I am not able to get data from each request sequentially.I mean when http req related to starbucks finishes i want to increment points and show loaded like its fetching points; after finishing starbucks req, uber req should fire and further want to increment points and show loaded like its fetching points.
I have tried below in subscribe; used interval because I want to show loader to move till end ;however its not working properly.
let timer = Observable.interval(200).subscribe(() => {
this.merchantName = res.initiator;
this.points++;
this.counter++;
this.percentage = (this.counter / 20) * 100;
if (this.percentage === 100) {
timer.unsubscribe();
this.percentage = 0;
this.counter = 0;
}
});
Am I using correct operator ? concateMap ? Is there any alternative to achieve above ?
Upvotes: 0
Views: 1081
Reputation: 23463
Mapping operator
Using concatMap
vs flatMap
in your app depends if you want to keep the ordering of merchants.
Remember http is async, so likely some calls will be responding quicker than others. concatMap
keeps the original ordering, whereas flatMap
emits as soon as response arrives.
Otherwise, they both do the same job - unwind inner observables.
Timer
Although technically nothing wrong with the timer code, I think it's confusing the picture. Basically, the http responses are not going to arrive smoothly, so unless you know that responses will always take less than 200ms, you can't expect this timer to be emitting at 200ms intervals.
Results of Observable.of(...this.merchantsList).concatMap(
not able to get data from each request sequentially
You explained what you want to get, but not what you are getting. I can't see why you wouldn't get sequential results, especially using concatMap
.
Try commenting out the timer code and put in a console.log(res)
to see what order of responses you get.
.. so two intervals are running concurrently as they are async !.. wondering is there any way to run interval also sequentially
So, I think this block within the outer subscribe
let timer = Observable.interval(200).subscribe(() => {
...
}
runs fast and returns back to the next merchant response. The inner subscription is running independently (as you say async). Without that interval wrapper, the inner part would run synchronously, and everything would be in sequence. But I guess you want a small delay for the correct on-screen effect.
Maybe you need the interval outside the subscribe instead of inside, like this
}).delay(200).subscribe((res) => {
Using combineLatest()
This is what I have so far for this approach.
const merchantPoints$ = Observable.of(...this.merchantsList)
.concatMap((merchant: Object) => {
return this.commonService.getAllMerchatPoints(merchant, data).map((res) => {
return res
});
});
const timer$ = Observable.interval(200);
Observable.combineLatest(merchantPoints$, timer$)
.subscribe(combined => {
const res = combined[0];
this.merchantName = res.initiator;
this.points++;
this.counter++;
this.percentage = (this.counter / 20) * 100;
if (this.percentage === 100) {
// timer.unsubscribe();
this.percentage = 0;
this.counter = 0;
}
});
Upvotes: 1