randombits
randombits

Reputation: 48450

Retrieve data from RxJS observable in HttpModule

I am failing to understand how to map the data properties out of HttpService in my NestJS application. To my understanding, this Observable just wraps axios. Here's some example code:

interface Todo {
   task: string,
   completed: false
}

import {
  Injectable,
  HttpService,
  Logger,
  NotFoundException,
} from '@nestjs/common'
import { map } from 'rxjs/operators

async getTodo(todoUrl: string): Todo {
   const resp = this.httpService
      .get('https://example.com/todo_json')
      .pipe(map(response => response.data)) // map task/completed properties?
   return resp
}

resp in this case seems to be of type Observable. How do I retrieve just the data properties I want using map on this request to return my Todo interface?

Upvotes: 3

Views: 8561

Answers (4)

Jay McDoniel
Jay McDoniel

Reputation: 70131

Nest by default will subscribe to the observable for you it your return the Observable from your service. As this can be the case you can do something like

@Injectable()
export class TodoService {

  constructor(private readonly http: HttpService) {}

  getTodos(todoUrl: string): Observable<Todo> {
    return this.http.get(todoUrl).pipe(
      map(resp => resp.data),
    );
  }

}

And so long as you have a controller class calling this.todoSerivce.getTodos(todoUrl) and returning it, the response will be sent out.

However, if you want to instead make it a promise as you are more accustomed to them, you can tack on a .toPromise() method to the observable chain and now it it awaitable (though it will be slower because it has to wait for the observable to emit its complete event).

Example with .toPromise():

@Injectable()
export class TodoService {

  constructor(private readonly http: HttpService) {}

  getTodos(todoUrl: string): Todo {
    const myTodo = await this.http.get(todoUrl).pipe(
      map(resp => resp.data),
    ).toPromise();
    return myTodo;
  }

}

Edit 1/20/22

In RxJS@^7, toPromise() is deprecated and will be removed in v8. Instead, you can use lastValueFrom to wrap the entire observable

@Injectable()
export class TodoService {

  constructor(private readonly http: HttpService) {}

  getTodos(todoUrl: string): Todo {
    const myTodo = await lastValueFrom(this.http.get(todoUrl).pipe(
      map(resp => resp.data),
    ));
    return myTodo;
  }

}

Upvotes: 8

Christian Balseca
Christian Balseca

Reputation: 1

async getTodo(todoUrl: string): Todo {
    const resp = await this.httpService
      .get('https://example.com/todo_json')
      .toPromise();
    return resp.data;
}

Upvotes: -1

DeborahK
DeborahK

Reputation: 60518

Looking at your code:

import { map } from 'rxjs/operators' 

async getTodo(todoUrl: string): Todo {
   const resp = this.httpService
      .get('https://example.com/todo_json')
      .pipe(map(response => response.data)) // map task/completed properties?
   return resp
}

getTodo returns an Observable, not the response. So your return value should be Observable<Todo>.

The code should look more like this:

  getTodo(): Observable<Todo> {
    return this.http.get<Todo>('https://example.com/todo_json')
      .pipe(
        map(response => response.data),
        catchError(this.handleError)
      );
  }

EDIT: You can't just return the data from this method because it is asynchronous. It does not have the data yet. The method returns an Observable ... which is basically a contract saying that it will (at some later time) return the data for you.

Upvotes: 1

Adrian Brand
Adrian Brand

Reputation: 21638

Async functions need to return a promise, you can call toPromise on an observable to return a promise.

async getTodo(todoUrl: string): Todo {
   const resp = this.httpService
      .get('https://example.com/todo_json')
      .pipe(map(response => response.data)) // map task/completed properties?
   return resp.toPromise();
}

Upvotes: 0

Related Questions