41

I'm trying fetch data via REST with angular 2 HttpClient. I'm following the angular tutorial here https://angular.io/tutorial/toh-pt6 and under the Heroes and HTTP section you'll see this snippet of code used to fetch hero data via http.

getHeroes(): Promise<Hero[]> {
  return this.http.get(this.heroesUrl)
         .toPromise()
         .then(response => response.json().data as Hero[])
         .catch(this.handleError);
}

And below is a similar version I wrote in my application

fetch(startIndex: number, limit: number): Promise<Order[]> {
    let url = this.baseUrl + "&startIndex=" + startIndex + "&limit=" + limit;

    return this.http.get(url)
                .toPromise()
                .then(response => response.json().results as Order[])
                .catch(this.handleError);
}

I'm using InteliJ Idea and there's a red line on the call response.json() and even when I try to build using ng build I get the error.

Property 'json' does not exist on type 'Object'.

You may notice that instead of json().data I have json().results. That's because according to the tutorial the server responded with an object that has a data field but my own server responds with an object that has a results field. If you scroll down the tutorial a bit you'll see this point.

Note the shape of the data that the server returns. This particular in-memory web API example returns an object with a data property. Your API might return something else. Adjust the code to match your web API.

In an attempt to fix this, I tried something like this

(response: Response) => response.json().results as Order[]

When I did that the .json() method was been resolved but another error popped up that

Property results does not exist on type Promise

I tried fixing that by defining an interface

interface OrderResponse {
    orders: Order[];
}

And modified the get call to

 .get<OrderResponse>(url)...

But that also didn't work. Another error popped up

Type 'OrderResponse' is not assignable to type 'Response'.

One thing note is that, in the tutorial they used the Angular HttpModule but in my application I'm using the new Angular HttpClientModule so maybe that's where the error is coming.

I'm new to Angular 2 and this is the first app I'm building with it. If the above code is no longer valid with the new HttpClientModule I'd appreciate any help on how to achieve the same with the new HttpClientModule.

I found similar questions Property 'json' does not exist on type '{}' and Property does not exist on type 'object' but none of the answers there helped me.

Update

As the comments suggested there is no .json() method in the new HttpClientModule. I'd still appreciate help on how to achieve the same effect with the new module. From the guide they did something like this

http.get<ItemsResponse>('/api/items').subscribe(data => {
  // data is now an instance of type ItemsResponse, so you can do this:
  this.results = data.results;
});

Which I understand perfectly but my problem is, that code is not within a component but a service so calling subscribe and assigning the result to a instance field won't make much sense.

I need my service to return an array of Orders wrapped in a Promise. The my components can just make calls like

this.orderService.fetch(0, 10).then(orders => this.orders = orders)

I also thought of declaring a local variable in my service fetch method so that I can do

.subscribe(data => {
    this.orders = data.results;
}
// and out of the get call I return my local variable orders like
return Promise.resolve(orders)

But that doesn't make much sense to me as the call to .get() is asynchronous and the method may return even before all the data is fetch and the orders array may be empty.

Update

As requested here is the code for handleError

private handleError(error: any): Promise<any> {
    console.log('An error occured ', error);
    return Promise.reject(error.message || error);
}
Stylishcoder
  • 995
  • 1
  • 7
  • 19
  • 4
    There is no .json() method in the HttpClient API. The tutorial that you are following uses the old Http API. Please check https://angular.io/guide/http – Jota.Toledo Sep 01 '17 at 17:40
  • 2
    Like @Jota.Toledo said, there are two http clients - Http and HttpClient. HttpClient is the new one, which no longer uses the .json() method. – matmo Sep 01 '17 at 17:43
  • @matmo thanks for your input. I updated my question. Please have a look. Thanks – Stylishcoder Sep 01 '17 at 17:57
  • why do you need to return a promise? observables are better if you ask me – Jota.Toledo Sep 01 '17 at 18:01
  • Can you answer the question with a solution that returns an observable then? I just need the service the return an array of Orders I really don't care if it's wrapped in a Promise or an observable. I'm still getting a hang of these concepts. Or is my question not clear enough? – Stylishcoder Sep 01 '17 at 18:02
  • @ivange94 added an answer – Jota.Toledo Sep 01 '17 at 18:12

4 Answers4

113

For future visitors: In the new HttpClient (Angular 4.3+), the response object is JSON by default, so you don't need to do response.json().data anymore. Just use response directly.

Example (modified from the official documentation):

import { HttpClient } from '@angular/common/http';

@Component(...)
export class YourComponent implements OnInit {

  // Inject HttpClient into your component or service.
  constructor(private http: HttpClient) {}

  ngOnInit(): void {
    this.http.get('https://api.github.com/users')
        .subscribe(response => console.log(response));
  }
}

Don't forget to import it and include the module under imports in your project's app.module.ts:

...
import { HttpClientModule } from '@angular/common/http';

@NgModule({
  imports: [
    BrowserModule,
    // Include it under 'imports' in your application module after BrowserModule.
    HttpClientModule,
    ...
  ],
  ...
Voicu
  • 13,512
  • 9
  • 51
  • 60
  • 2
    really simple explanation with a simple and clear example! If every one would give such helpful answers, it would be great. – k.vincent Mar 01 '18 at 08:15
11

UPDATE: for rxjs > v5.5

As mentioned in some of the comments and other answers, by default the HttpClient deserializes the content of a response into an object. Some of its methods allow passing a generic type argument in order to duck-type the result. Thats why there is no json() method anymore.

import {throwError} from 'rxjs';
import {catchError, map} from 'rxjs/operators';

export interface Order {
  // Properties
}

interface ResponseOrders {
  results: Order[];
}

@Injectable()
export class FooService {
 ctor(private http: HttpClient){}

 fetch(startIndex: number, limit: number): Observable<Order[]> {
    let params = new HttpParams();
    params = params.set('startIndex',startIndex.toString()).set('limit',limit.toString());
    // base URL should not have ? in it at the en
    return this.http.get<ResponseOrders >(this.baseUrl,{
       params
    }).pipe(
       map(res => res.results || []),
       catchError(error => _throwError(error.message || error))
    );
} 

Notice that you could easily transform the returned Observable to a Promise by simply invoking toPromise().

ORIGINAL ANSWER:

In your case, you can

Assumming that your backend returns something like:

{results: [{},{}]}

in JSON format, where every {} is a serialized object, you would need the following:

// Somewhere in your src folder

export interface Order {
  // Properties
}

import { HttpClient, HttpParams } from '@angular/common/http';
import { Observable } from 'rxjs/Observable';
import 'rxjs/add/operator/catch';
import 'rxjs/add/operator/map';

import { Order } from 'somewhere_in_src';    

@Injectable()
export class FooService {
 ctor(private http: HttpClient){}

 fetch(startIndex: number, limit: number): Observable<Order[]> {
    let params = new HttpParams();
    params = params.set('startIndex',startIndex.toString()).set('limit',limit.toString());
    // base URL should not have ? in it at the en
    return this.http.get(this.baseUrl,{
       params
    })
    .map(res => res.results as Order[] || []); 
   // in case that the property results in the res POJO doesnt exist (res.results returns null) then return empty array ([])
  }
} 

I removed the catch section, as this could be archived through a HTTP interceptor. Check the docs. As example:

https://gist.github.com/jotatoledo/765c7f6d8a755613cafca97e83313b90

And to consume you just need to call it like:

// In some component for example
this.fooService.fetch(...).subscribe(data => ...); // data is Order[]
Jota.Toledo
  • 22,272
  • 8
  • 48
  • 63
  • Thanks. Seems the code assumes my server will return an array of orders. My server actually returns an object with a .results field which is the array of orders. Something like {results: []}. – Stylishcoder Sep 01 '17 at 18:13
  • Ill update, @ivange94 but I think the best would be to remove that capsulation level on your backend – Jota.Toledo Sep 01 '17 at 18:15
  • Also catch returns an error that property catch does not exist on type Observable – Stylishcoder Sep 01 '17 at 18:16
  • Sorry for the trouble. I didn't take not of this import 'rxjs/add/operator/catch'; let me test and get back to you. Thanks so much. – Stylishcoder Sep 01 '17 at 18:24
  • Thanks. I'm accepting your answer. You might want to change res.results to res['results'] as typescript will complain that field 'results' not found. I think that's cleaner than defining an interface. Thank you very much for you help. – Stylishcoder Sep 01 '17 at 18:42
  • Also can you help me understand what this Order[] || [] is doing? The code works perfectly but I'd like to understand what I've written. Thanks. – Stylishcoder Sep 01 '17 at 18:44
  • Let us [continue this discussion in chat](http://chat.stackoverflow.com/rooms/153492/discussion-between-ivange94-and-jota-toledo). – Stylishcoder Sep 01 '17 at 18:44
1

The other way to tackle it is to use this code snippet:

JSON.parse(JSON.stringify(response)).data

This feels so wrong but it works

Muhammad Dyas Yaskur
  • 4,300
  • 8
  • 27
  • 48
0

fix for me on dotnet API angular MVC application

check your api url, trailing /api/ was removed when i updated the url. without it the root url is returning html, that is the <