Reputation: 3293
I have a case where I may or may not need to add observables to a list. I then want to forkJoin
the observables I do have so the page can load once all of the data is available.
let observables: Observable<any>[] = [];
observables.push(this.taskService.getStep(this.housingTransactionId, this.task.stageReferenceId, this.task.stepReferenceId));
if (this.task.associatedChatThreadId) {
observables.push(this.messageHubService.getChatThread(this.housingTransactionId, this.task.associatedChatThreadId));
}
if (this.task.associatedDocuments && this.task.associatedDocuments.length > 0) {
this.task.associatedDocuments.forEach(documentId => {
observables.push(this.documentHubService.getDocumentProperties(this.housingTransactionId, documentId));
});
}
Observable.forkJoin(observables)
.subscribe(([step, chatThread, ...documents]) => {
this.step = step;
this.chatThread = chatThread;
this.documents = documents;
this.isPageLoading = false;
}, error => {
this.isPageLoading = false;
console.log(error);
});
The problem I'm getting is that if I don't have a this.task.associatedChatThreadId
, then the observable is not added to the list and when the forkJoin
is executed, the ...documents
are in the position of the chatThread
property in the subscribe method (well, the first document!).
Is there a way to ensure the positioning of the responses from a forkJoin
? Or should I/can I use a different approach?
Upvotes: 5
Views: 5249
Reputation: 1734
You could make a helper function that accepts an object which has string keys and observable values and returns an observable that will emit an object with the same keys, but having the resulting values instead of the observables as values.
I would not really say that this is a cleaner version than using of(null) like suggested by martin, but it might be an alternative.
function namedForkJoin(map: {[key: string]: Observable<any>}): Observable<{[key: string]: any}> {
// Get object keys
const keys = Object.keys(map);
// If our observable map is empty, we want to return an empty object
if (keys.length === 0) {
return of({});
}
// Create a fork join operation out of the available observables
const forkJoin$ = Observable.forkJoin(...keys.map(key => map[key]))
return forkJoin$
.map(array => {
const result = {};
for (let index = 0; index < keys.length; index++) {
result[keys[index]] = array[index];
}
}));
}
Please keep in mind, I did not have angular or rxjs running here at the moment, so I could not verify the function really works. But the idea is: 1. Get the keys from the input map. 2. Use the keys to get an array of observables and pass that to fork join. 3. Add a mapping function that converts the resulting array back into an object.
Upvotes: 0
Reputation: 1
Another approach would be not to use folkJoin but subscribe separately. At the same time, make isPageLoading a BehaviorSubject which counts how many async requests you currently have. Each time when you make a request, you can have isPageLoading.next(1), and isPageLoading.next(-1) when you finish a request.
Upvotes: 0
Reputation: 96959
Most easily you can add a dumb Observable.of(null)
with null
value if the condition is not met in order to keep the same order of responses:
if (this.task.associatedChatThreadId) {
observables.push(this.messageHubService....);
} else {
observables.push(Observable.of(null))
}
Then in the subscription you can check if chatThread === null
becauese it'll always be present at the same position.
Alternatively, you could wrap each Observable in observables
with some extra object that would make it uniquely identifiable in the subscriber but that would be unnecessarily complicated so I'd personally stick to the first option.
Upvotes: 12