16

I have this function:

  getData(): Observable<ServerResponse> {
    const data = {} as ServerResponse;

    data.client = this.getClientData().pipe(
      map(response =>
        response.map(x => {
          return x.data.client;
        }),
      ),
    );

    data.otherData = this.otherData().pipe(
      map(response =>
        response.map(x => {
          return this.groupByTimePeriod(x.data.other, 'day', 'week');
        }),
      ),
    );
  }

The function need to return data with all the properties that gets assigned to that inside a function. How would you do that?

If I just do return data it is does not work as this.getClientData() and this.otherData() has not yet being completed.

1 Answer 1

30

Well, you have multiple questions/problems here. I'll start with the easiest. How do you get an observable from a function/object? The answer is via observable of:

return of(data);

But you eluded to a larger problem, which is: how do you defer returning data until child observables emit their values? You are looking for forkJoin. Via the docs:

forkJoin will wait for all passed Observables to complete and then it will emit an array with last values from corresponding Observables. So if you pass n Observables to the operator, resulting array will have n values, where first value is the last thing emitted by the first Observable, second value is the last thing emitted by the second Observable and so on. That means forkJoin will not emit more than once and it will complete after that.

You also have several other problems. For example, you are never subscribing to this.getClientData() or this.otherData(). Observables are lazily executed. Code in your observable will not execute until something subscribes to it. From the docs:

The code inside Observable.create(function subscribe(observer) {...}) represents an "Observable execution", a lazy computation that only happens for each Observer that subscribes.

It also appears as though you are using pipe/map in an attempt to set a property on your data object. But you're never setting data.client or data.other, so they will always be empty.

So, putting it all together, here's what your code might look like, with simulated server latency to show that forkJoin waits for completion of both observables:

import { Injectable } from '@angular/core';
import { Observable, of, forkJoin } from 'rxjs';
import { delay } from 'rxjs/operators';

@Injectable({
    providedIn: 'root'
})
export class TestService {
    getData(): Observable<ServerResponse> {
        const allOperations = forkJoin(
            this.getClientData(),
            this.getOtherData()
        );

        const observable = Observable.create(function subscribe(observer) {
            // Wait until all operations have completed
            allOperations.subscribe(([clientData, otherData]) => {
                const data = new ServerResponse;
                // Update your ServerReponse with client and other data
                data.otherdata = otherData.other;
                data.client = clientData.client;
                // Now that data is 100% populated, emit to anything subscribed to getData().
                observer.next(data);
                observer.complete();
            });

        });
        // We return the observable, with the code above to be executed only once it is subscribed to
        return observable;
    }

    getClientData() : Observable<any> {
        return of({ client: 'Client 1' });
    }
    getOtherData(): Observable<any> {
        // Fake server latency
        return of({ other: 'Other data that takes a while to return from server...' })
            .pipe(delay(2000));
    }
}

export class ServerResponse {
    client: string;
    otherdata: string;
}

If you call getData() and subscribe to the observable, you will see that forkJoin works as intended and we have to wait 2 seconds for both child observables to complete and our observable to emit a value:

this.testService.getData().subscribe(data => {
  console.log(data);
});

It seems that you might be new to RxJS / asynchronous programming. I suggest reading the excellent introduction to RxJs when you get a chance. It can be tricky at first but with practice this will come to be second nature.

Sign up to request clarification or add additional context in comments.

2 Comments

Thank you for providing a solution with a such detailed explanation. I will try it out and see if it is works.
Excellent! But the syntax has changed, so to create the observable (in Typescript anyway) it's now: const observable:Observable<MyClass> = new Observable((observer:Observer<MyClass>) => { ...

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.