Angular2 - TypeError: Cannot read property 'Id' of undefined in (Typescript)

Niklas picture Niklas · May 8, 2016 · Viewed 20.2k times · Source

I'm getting the following error:

angular2.dev.js:23925 EXCEPTION: TypeError: Cannot read property 'Id' of null in [


{{ product.Id }}
 in ProductEditComponent@0:68]

thrown with:

//Product-edit.component.ts:

import {Component } from 'angular2/core';
import { IProduct } from './product'
import { ProductService } from './product.service'
import { RouteParams } from 'angular2/router';
@Component({
  template:`<div class="wrapper wrapper-content animated fadeInRight ecommerce"> 
              {{ product.Id }}
            </div>`, 
})
export class ProductEditComponent{
    product: IProduct = null;
    errorMessage: string;
    constructor(private _routeParams: RouteParams, private _productService: ProductService){

    }

    ngOnInit(): void {
        this._productService.getProduct(this._routeParams.get('id'))
            .subscribe(
                product => this.product = product,
                error => this.errorMessage = <any>error);


    }

}

ProductService:

getProduct(id: string): Observable<IProduct> {
    return this._http.get(this._baseUrl + this._getProductUrl + '/' + id)
        .map((response: Response) => <IProduct>response.json())
        .do(data => console.log("All: " + JSON.stringify(data)))
        .catch(this.handleError);
}

Response from server:

{"Id":"34d4efcy6","ExternalId":null,"UserId":"testing","ProductProvider":null,"Title":"Flaska vin","Desc":null,"MinDeliveryTime":null,"MaxDeliveryTime":null,"FreightCost":null,"Brand":null}

What am I messing up?

Answer

drew moore picture drew moore · May 8, 2016

In your component, you're initializing product to null, and then referencing product.Id in your template. The error occurs when Angular tries to draw your template initially, before your async call returns - at which point product is still null, thus the error: Cannot read property 'Id' of null.

The most immediate solution is to use the Elvis operator, which Angular provides for situations precisely like this. You'd use it by replacing {{ product.Id }} with {{ product?.Id }} in your template.

That said, you're likely to run into change detection problems with this approach, and in general you'll be much better off with an approach like:

export class ProductEditComponent{
  product: Observable<IProduct>; //product is an Observable
  errorMessage: string;
  constructor(private _routeParams: RouteParams, private _productService: ProductService){
     //product is always defined because you instantiate it in the ctor
     this._productService.getProduct(this._routeParams.get('id'));
  }

You'd then use {{(product | async).Id }} in place of {{product.Id}} in your template, leveraging AsyncPipe to let angular handle the dirtywork of subscribing and updating the UI as needed for you.