Pradeepl
Pradeepl

Reputation: 308

Angular Service - Type of object returned is object and not that of the generic type specified

I have an angular5 service which does an HTTP get and returns a specific type as shown below

public getProductByIDproductId: string): Observable<Product> {
    const headers = new HttpHeaders({ "Content-Type": "application/json" });
    const url = `${environment.productservice_baseurl}/${productId}`;
    return this.http
      .get<Product>(url, { headers: headers })
      .pipe(       
        tap(data => (this._product = data)),
        catchError(this.handleError)
      );
  }

The Product class is a class which has properties and methods. A small excerpt is below.

class Product{
    productID:string;
    productName:string;

    setQuantity(quantity:number){

    }
}

When I call the setQuantity function on this._product returned by the get I get a 'setQuantity is not a function' error. When I try to check the instance of this._product, it is not of type Product but if type Object.

Is the generic type set on the get method only to help compile-time type checking? How do I get a concrete instance of the product class from getProductByIDproductId method?

Upvotes: 3

Views: 5325

Answers (3)

Oscar Paz
Oscar Paz

Reputation: 18312

You can't do what you're doing.

When you fetch data from an URL, you get just JSON. You're telling TypeScript that data is of type Product, but that is just a hint for the compiler and does not make it true.

data was not created with a call to new Product and so it doesn't share its methods.

If you want your this._product to behave like a native Product instance, you can do this:

this._product = data;
Object.setPrototypeOf(this._product, Product.prototype);

This way you turn this._product into a real Product instance, including its methods.

Another option, if you are worried about setPrototypeOf and its potential performance drawback, is doing it this way:

this._product = new Product();
Object.assign(this._product, data);

Of course, this is only a good idea if Product has a parameterless constructor. And, in any case, if data has properties not defined in Product class you can also run into performance issues.

Upvotes: 5

user4676340
user4676340

Reputation:

You could map the response for instance.

return this.http
  .get<Product>(url, { headers: headers })
  .map(response => new Product(response)) // I don't know your constructor
  .pipe(       
    tap(data => (this._product = data)),
    catchError(this.handleError)
  );

This way, you're sure that your object will be a Typescript object that you defined.

EDIT You can create an object "by hand" with only a few lines :

export class Product {
  // Your class ...

  public static createProductFromHttpResponse = (response: any) => {
    const ret = new Product();
    for (let key in response.key) { ret[key] = response[key]; }
    return ret;
  }
}

In your mapping :

  .map(response => Product.createProductFromHttpResponse(response))

Upvotes: 2

severus256
severus256

Reputation: 1723

As I know, to specify response type you should use interfaces instead classes, so your product should be an interface:

interface Product{
productID:string;
productName:string;

}

And as it is just interface and http response it should not have any methods

EDIT

The answer from @trichetriche probably one of the solution for this issue.

Upvotes: 0

Related Questions