Chris
Chris

Reputation: 3293

Conditional observables with forkJoin

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

Answers (3)

tom van green
tom van green

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

Q. Liu
Q. Liu

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

martin
martin

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

Related Questions