angular - Egenskapen 'json' finns inte på typen 'Object'


Jag försöker hämta data via REST med Angular 2 HttpClient. Jag följer Angular-handledningen här https://angular.io/tutorial/toh-pt6 och under avsnittet Heroes and HTTP kommer du att se detta kodavsnitt som används för att hämta hjältedata via http.

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

Och nedan är en liknande version som jag skrev i min ansökan

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);
}

Jag använder InteliJ Idea och det finns en röd linje på anropet response.json() och även när jag försöker bygga med ng build får jag felet.

Egenskapen 'json' finns inte på typen 'Object'.

Du kanske märker att istället för json().data har jag json().results. Det beror på att enligt handledningen svarade servern med ett objekt som har ett data-fält, men min egen server svarar med ett objekt som har ett results-fält. Om du bläddrar ner lite i handledningen kommer du att se denna punkt.

Observera formen på de data som servern returnerar. Detta särskilda exempel på ett web-API i minnet returnerar ett objekt med en data-egenskap. Ditt API kanske returnerar något annat. Anpassa koden för att matcha ditt web-API.

I ett försök att fixa detta, försökte jag något liknande detta

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

När jag gjorde det löstes .json()-metoden men ett annat fel dök upp som

Egenskapsresultaten finns inte på typen Promise

Jag försökte fixa det genom att definiera ett gränssnitt

interface OrderResponse {
    orders: Order[];
}

Och ändrade get-anropet till

 .get<OrderResponse>(url)...

Men det fungerade inte heller. Ett annat fel dök upp

Typen 'OrderResponse' kan inte tilldelas typen 'Response'.

En sak att notera är att, i handledningen använde de Angular HttpModule men i min applikation använder jag det nya Angular HttpClientModule så kanske det är där felet kommer ifrån.

Jag är ny på Angular 2 och detta är den första appen jag bygger med det. Om koden ovan inte längre är giltig med det nya HttpClientModule skulle jag uppskatta all hjälp med hur man uppnår samma sak med det nya HttpClientModule.

Jag hittade liknande frågor Property 'json' finns inte på typen '{}' och Property finns inte på typen 'object', men inget av svaren där hjälpte mig.

Uppdatering

Som kommentarerna föreslog finns det ingen .json()-metod i den nya HttpClientModule. Jag skulle fortfarande uppskatta hjälp med hur man uppnår samma effekt med den nya modulen. Från guiden gjorde de något liknande detta

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

Vilket jag förstår perfekt men mitt problem är att den koden inte är inom en komponent utan en tjänst, så att anropa subscribe och tilldela resultatet till ett instansfält skulle inte ha mycket mening.

Jag behöver att min tjänst returnerar en array med beställningar omslutna i ett Promise. Mina komponenter kan helt enkelt göra anrop som

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

Jag tänkte också på att deklarera en lokal variabel i min servicehämtningsmetod så att jag kan göra

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

Men det verkar inte riktigt vettigt för mig eftersom anropet till .get() är asynkront och metoden kan returnera innan all data har hämtats och orders-arrayen kan vara tom.

Uppdatering

Som begärt här är koden för handleError

private handleError(error: any): Promise<any> {
    console.log('An error occured ', error);
    return Promise.reject(error.message || error);
}

Svar #1

För framtida besökare: I den nya HttpClient (Angular 4.3+), är response-objektet JSON som standard, så du behöver inte längre göra response.json().data. Använd helt enkelt response direkt.

Exempel (modifierat från den officiella dokumentationen):

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));
  }
}

Glöm inte att importera det och inkludera modulen under imports i ditt projekts app.module.ts:

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

@NgModule({
  imports: [
    BrowserModule,
    // Include it under 'imports' in your application module after BrowserModule.
    HttpClientModule,
    ...
  ],
  ...

Svar #2

UPPDATERING: för rxjs > v5.5

Som nämnts i några av kommentarerna och andra svar, deserialiserar HttpClient som standard innehållet i ett svar till ett objekt. Några av dess metoder tillåter att man skickar en generisk typargument för att duck-typa resultatet. Därför finns det inte längre någon json()-metod.

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))
    );
} 

Observera att du enkelt kan omvandla det returnerade Observable till en Promise genom att helt enkelt anropa toPromise().

ORIGINAL ANSWER:

I ditt fall kan du

Antag att din backend returnerar något som:

{results: [{},{}]}

i JSON-format, där varje {} är ett serialiserat objekt, skulle du behöva följande:

// 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 ([])
  }
} 

Jag tog bort catch-avsnittet, eftersom detta kan hanteras genom en HTTP-interceptor. Kolla dokumentationen. Som exempel:

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

Och för att konsumera behöver du bara anropa det så här:

// In some component for example
this.fooService.fetch(...).subscribe(data => ...); // data is Order[]

Relaterade artiklar