1
votes

I have searched in SO and found similar questions around Observables, but none helped my specific use-case, which, I have to assume is a pretty common use-case.

I am calling a REST API (via the http module) to get a Quote object. Naturally it returns an Observable<Quote>.

I am also calling another REST API (again via the http module) to get QuoteLineItem[]. Again, this naturally returns an Observable<QuoteLineItem[]>.

Ideally the client code should only be retrieving Observable<Quote> and be able to use Quote.LineItems.

What is the best practice for this? I can come up with two options:

1- Populate Quote.LineItems from the Observable<QuoteLineItem[]> when the Observable is subscribed to. I have been trying to get this done, but I don't seem to be doing that. Here is the code, but not sure what is wrong?

    // This will change to an http call
    const subj: BehaviorSubject<Quote> = new BehaviorSubject(quote) 
    const obs: Observable<Quote> = subj.asObservable();
    // Now try to get LineItems within the observable and populate Quote.LineItems
    obs.pipe(switchMap((q: Quote) => {
      this.quoteLineItemService.getLineItems(quoteId).subscribe((data: QuoteLineItem[]) => {
        q.lineItems = data
        return q
      },
      error => console.error(error)
      )
    }))
    return obs;

This code is getting a compilation error: Argument of type '(q: any) => void' is not assignable to parameter of type '(value: any, index: number) => ObservableInput'. Type 'void' is not assignable to type 'ObservableInput'.

My first question is, what am I doing wrong here? How can I get that done?

2- Is the above the best practice or should I have Observable<QuoteLineItem[]> instead of QuoteLineItem[] as a Property within the Quote object?

Sorry if these are very basic questions. Your help, as always would be very much appreciated!

2
You may find some inspiration reading this - Picci

2 Answers

0
votes

Option 1 seems reasonable enough to me. The only issue is the argument type of the callback should be switchMap((q: Quote)... with a capital Q instead of switchMap((q: quote). Interface/type names are case sensitive. And if I understand correctly, your model is called Quote and you have member variable called quote. That I believe is the source of the error.

0
votes

The two calls option is not bad at all if you have no access to the backend service you are calling to return a fulfilled model.

To populate you Quote object you can return a Promise of Quote to simplify your code.
Then, make the double request to the server.

const getQuote = (id: any): Promise<Quote> => {
    return new Promise<Quote>((resolve, reject) => {
        const obs: Observable<Quote> = subject.asObservable(); // Mocked HTTP call

        obs.subscribe((q: Quote) => {
            quoteLineItemService.getLineItems(q.id).subscribe(
                (items: QuoteLineItem[]) => {
                    q.items = items;
                    resolve(q);
                },
                (err: Error) => {
                    console.error(err);
                    reject(err);
                }
            );
        });
    });
}

getQuote(1)
    .then((q: Quote) => {
        // Populated quote
        console.log(q);
    })
    .catch(_ => {/* handle error */});