1

I have been following the tutorial of the heroes in angular, and then I wanted to apply it to my small test project to consolidate it. When I was using it with the built in http server and some mock items, it worked, but now I am not able to retrieve the items I want from another a Django REST API.

I can test that this is my API and works:

curl -H 'Accept: application/json; indent=4' http://127.0.0.1:5000/inventory/items_viewset/ -L
{
    "count": 13,
    "next": "http://127.0.0.1:5000/inventory/items_viewset/?page=2",
    "previous": null,
    "results": [
        {
            "label": "00004",
            "name": "Test2",
            "notes": "222",
            "owner": "admin",
            "photo": null
        },
        {
            "label": "0000007",
            "name": "Test1",
            "notes": null,
            "photo": "http://127.0.0.1:5000/media/item_images/2021/02/19/IMG_20210207_134147.jpg"
        },
    ]
}

Then I have my Item item.ts

export interface Item {
  label: string;
  name: string;
  notes: string;
}

I have a component displaying those items items.component.ts:

    items: Item[];  

    constructor(private itemService: ItemService, private messageService: MessageService) { }
    // constructor() { }

    ngOnInit(): void {
        this.getItems();
    }

    
    getItems(): void {
        this.itemService.getItems()
            .subscribe(items => this.items = items);
    }

And here I have the service where I am trying to do the changes

import { Injectable } from '@angular/core';
import { Item } from './item';
import { ITEMS } from './mock-items';
import { Observable, of } from 'rxjs';
import { MessageService } from './message.service';
import { HttpClient, HttpHeaders } from '@angular/common/http';
import { catchError, tap, map } from 'rxjs/operators';

@Injectable({
  providedIn: 'root'
})
export class ItemService {

    constructor(  private http: HttpClient, private messageService: MessageService) { }
  
    private itemsUrl = 'http://127.0.0.1:5000/inventory/items_viewset';  // URL to web api
    
    httpOptions = {
        headers: new HttpHeaders({ 'Content-Type': 'application/json',  })
    };
  
    getItems(): Observable<Item[]> {
return this.http.get<Item[]>(this.itemsUrl).pipe(
            map(res => res ),
            tap(_ => this.log('fetched items')),
            catchError(this.handleError<Item[]>('getItems', []))
        );  
    }
    
    
    /**
    * Handle Http operation that failed.
    * Let the app continue.
    * @param operation - name of the operation that failed
    * @param result - optional value to return as the observable result
    */
    private handleError<T>(operation = 'operation', result?: T) {
        return (error: any): Observable<T> => {

            // TODO: send the error to remote logging infrastructure
            console.error(error); // log to console instead

            // TODO: better job of transforming error for user consumption
            this.log(`${operation} failed: ${error.message}`);
            console.error(`${operation} failed: ${error.message}`);

            // Let the app keep running by returning an empty result.
            return of(result as T);
        };
    }
    
      /** Log a HeroService message with the MessageService */
    private log(message: string) {
        this.messageService.add(`ItemService: ${message}`);
    }
}

It may be a little bit messy, as I removed irrelevant stuff, but the only point where I have been working since I switched to the real API is this method:

getItems(): Observable<Item[]> {
return this.http.get<Item[]>(this.itemsUrl).pipe(
            map(res => res ),
            tap(_ => this.log('fetched items')),
            catchError(this.handleError<Item[]>('getItems', []))
        );  
    }

I have been already more than one day in my free time trying to fix this but I am not able to find how can I access the key results from the JSON in order to map it to the Item. Most of the similar questions I find make use of a function .json() which I think I have understood is not used anymore in HttpClient. Then I have also tried in many forms something like res.results without success, I get an exception that that property does not exist.

The query is working, as If I change the tap line to:

tap(_ => console.log(_)),

I get the Object in an error.

Can someone give me some hint on what I am doing wrong?. I don't know where else to search I can't make it work from the tutorial, and the documentation from https://angular.io/api/common/http/HttpClient I think does also not cover this, and I think it should be quiet simple, but I have no idea how can I further debug.

3
  • You've lied to HttpClient. The generic type you've supplied to http.get is not what you're expecting the API to return, which is why it's telling you there's no results property. Commented Feb 27, 2021 at 10:51
  • 2
    You could define an additional type, e.g. type Envelope<T> = { count: number, next: string | null, previous: string | null, results: T[] } and use that, but the compiler can only help you if it has accurate information. Commented Feb 27, 2021 at 10:56
  • Thank you @jonrsharpe. Could you provide an example on how to apply this concept to my getItems function? Commented Feb 27, 2021 at 11:28

1 Answer 1

1

I think I finally make it work but I don't know if this is the right approach if I want to use this function in many places as I need a lot of code in the component, but probably If I work on pagination request I would also need anyway the other parameters.

As suggested by @jonrsharpe I removed the Item and change it to any, and then I could do some parsing in the response in the component: Son in my service I have item.service.ts:

    getAllItems(): Observable<any> {
        return this.http.get(this.itemsUrl).pipe(
            tap(_ => this.log('fetched items')),
            catchError(this.handleError<Item[]>('getItems', []))
        );  
    }

And In my component I work with the JSON items.component.ts:

    items: Item[];  
    next: string;
    previous: string;

    ngOnInit(): void {
        this.getItems();
    }

    getItems() {
        this.itemService.getAllItems().subscribe(
            res => {
                console.log(res);
                if (res.results) {
                    this.items = res.results;
                }
                if (res.next) {
                    // set the components next transactions here from the response
                    this.next = res.next;
                    console.log(this.next);
                }
                if (res.previous) {
                    // set the components previous transactions here from the response
                    this.previous = res.previous;
                }
            }, 
            error => {console.log(error)}
        );
      }

I won't set the question as resolved, as I'm just a beginner and I have no idea if this is the right approach. But at least It seems to work in my scenario.

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

3 Comments

I didn't suggest removing the generic type entirely, either from getItems or http.get. The point was to change the generic type of http.get, to make it actually match what you're expecting the response to contain. Then the compiler can help you extract the right properties from the response to return an observable of Item[] from getItems.
As a general note, why are you conditionally assigning next, previous and results to the component properties? This could leave you in a state where you have results from request 1, next from request 2, and previous from request 3. You should just deal with the fact they may be undefined elsewhere in the application. After all, that's the state you'll have anyway if the first request fails.
@Jusmpty As I wrote I started learning angular recently so I don't have much background. But my idea was to build a table with items, and then use those next and previous to create pages of the table, while in other component just display the recent items. But again I am a complete beginner and maybe I am doing this wrong. I'd love to get another answer if this is not the correct approach.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.