Reputation: 53
@Input() list: string[];
ngOnInit() : void {
this.valueMap = new Map<any, any>();
this.getDataFromService();
this.buildContainer();
}
private getDataFromService(): void {
this.list.forEach(value-> {
this.fetchService(value).subscribe(data ->{
this.valueMap.set(value,data);
}
)
})
}
private buildContainer(): void {
console.log(this.valueMap.size); // shows always 0 even when service returns the data
}
Now thing is that I have to use this valueMap
in the method buidlContainer()
,
hence I need to get the complete map first from service. And when I use that map in buildConatainer() it shows me undefined .
I understood that this is some async call issue . Also , I can not call method buildContainer() for each value in .subscribe() as that would not be better idea. So , I have to compute the Map first before processing that further..
Any help is appreciated and i can not modify the service from returning Observable to return Promise
i want to do as below
private buildContainer(): void {
for([key,data] of this.valueMap) {
-----some code----
}
}
Upvotes: 2
Views: 1086
Reputation: 31105
This is one of the disadvantage (or advantage depending how you look at it) of introducing reactive paradigm to your code. A single point of reactive procedure would gradually creep into other parts of the code either partially or completely.
So I'd recommend you to make the valueMap
reactive as well and make it respond based on the changes in getDataFromService()
service. One way would to make the valueMap
variable a RxJS ReplaySubject
with a buffer of 1. So it'll buffer (or hold) it's last value pushed to it and emit it immediately upon subscription.
Try the following
import { forkJoin, ReplaySubject, Subject } from 'rxjs';
import { take, takeUntil } from 'rxjs/operators';
valueMap: ReplaySubject<Map<any, any>> = ReplaySubject<Map<any, any>>(1);
completed$ = new Subject<any>();
@Input() list: string[];
ngOnInit(): void {
this.valueMap = new Map <any, any>();
this.getDataFromService();
this.buildContainer();
}
private getDataFromService(): void {
forkJoin(this.list.map(value => this.fetchService(value))).pipe(
takeUntil(this.completed$) // <-- close subscription upon component `ngOnDestroy` hook trigger
).subscribe(
data => {
let map = new Map<any, any>();
data.forEach((item, i) => {
map.set(this.list[i], item);
});
this.valueMap.next(map);
}
);
}
private buildContainer(): void {
this.valueMap.asObservable().pipe(
take(1) // <-- emit only the first notification and complete
).subscribe(
data => {
console.log(this.valueMap.size);
}
);
}
ngOnDestroy() {
this.completed$.next();
this.completed$.complete();
}
I've also used RxJS forkJoin()
function to combine multiple observables and takeUntil
operator close the subscription when the component is destroyed.
Working example: Stackblitz
In the example, I've used jsonplaceholder
API to mock some HTTP calls in the fetchService()
function.
Note:
The forkJoin()
will emit only if all the observables complete. If you're dealing with a stream of data, you will have to replace it with either combineLatest
or zip
based on your requirement.
There is an obvious issue where if the buildContainer()
function is called multiple times in quick successions, each will trigger individual subscriptions.
Upvotes: 2
Reputation: 6887
Simply use forkJoin
to wait for all observables to finish. forkJoin
will bundle all observables into one. This observable can be returned and inside ngOnInit
you subscribe to it and call buildContainer
.
@Input() list: string[];
ngOnInit(): void {
this.valueMap = new Map<any, any>();
this.getDataFromService().subscribe(_ => this.buildContainer());
}
private getDataFromService(): Observable<any> {
return forkJoin(
this.list.map(value =>
this.fetchService(value).pipe(
tap(data => this.valueMap.set(value, data))
)
)
);
}
private buildContainer(): void {
console.log(this.valueMap.size);
}
The code would be even cleaner if you do not populate the valueMap
inside the tap
operator and instead use the result you get into your subscription function.
Upvotes: 3
Reputation: 1780
Remove buildContainer from ngOnInit and Call the method inside the subscribe
private getDataFromService(): void {
this.list.forEach((value, index) -> {
this.fetchService(value).subscribe(data ->{
this.valueMap.set(value,data);
}
)
if(this.list.length-1 == index){
this.buildContainer(); // call here
}
})
}
Upvotes: 0