Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Observable type error: cannot read property of undefined

In my Angular 2 application, I get an error:

Cannot read property 'title' of undefined.

This is a very simple component, just trying to get a bare minimum to work here. It hits my API controller (curiously multiple times), and it appears to hit the callback after an object is returned. My console.log outputs the object I would expect. Here is the full error:

TypeError: Cannot read property 'title' of undefined
    at AbstractChangeDetector.ChangeDetector_About_0.detectChangesInRecordsInternal (eval at <anonymous> (http://localhost:55707/lib/angular2/bundles/angular2.dev.js:10897:14), <anonymous>:31:26)
    at AbstractChangeDetector.detectChangesInRecords (http://localhost:55707/lib/angular2/bundles/angular2.dev.js:8824:14)
    at AbstractChangeDetector.runDetectChanges (http://localhost:55707/lib/angular2/bundles/angular2.dev.js:8807:12)
    at AbstractChangeDetector._detectChangesInViewChildren (http://localhost:55707/lib/angular2/bundles/angular2.dev.js:8877:14)
    at AbstractChangeDetector.runDetectChanges (http://localhost:55707/lib/angular2/bundles/angular2.dev.js:8811:12)
    at AbstractChangeDetector._detectChangesContentChildren (http://localhost:55707/lib/angular2/bundles/angular2.dev.js:8871:14)
    at AbstractChangeDetector.runDetectChanges (http://localhost:55707/lib/angular2/bundles/angular2.dev.js:8808:12)
    at AbstractChangeDetector._detectChangesInViewChildren (http://localhost:55707/lib/angular2/bundles/angular2.dev.js:8877:14)
    at AbstractChangeDetector.runDetectChanges (http://localhost:55707/lib/angular2/bundles/angular2.dev.js:8811:12)
    at AbstractChangeDetector.detectChanges (http://localhost:55707/lib/angular2/bundles/angular2.dev.js:8796:12)

The service (about.service.ts):

import {Http} from 'angular2/http';
import {Injectable} from 'angular2/core';
import {AboutModel} from './about.model';
import 'rxjs/add/operator/map';

@Injectable()
export class AboutService {
    constructor(private _http: Http) { }

    get() {
        return this._http.get('/api/about').map(res => {
            console.log(res.json()); // I get the error on the line above but this code is still hit.
            return <AboutModel>res.json();
        });
    }
}

The Component (about.component.ts):

import {Component, View, OnInit} from 'angular2/core';
import {AboutModel} from './about.model';
import {AboutService} from './about.service';
import {HTTP_PROVIDERS} from 'angular2/http';

@Component({
    selector: 'about',
    providers: [HTTP_PROVIDERS, AboutService],
    templateUrl: 'app/about/about.html'
})

export class About implements IAboutViewModel, OnInit {
    public about: AboutModel;

    constructor(private _aboutService: AboutService) {}

    ngOnInit() {    
        this._aboutService.get().subscribe((data: AboutModel) => {
            this.about = data;
        });
    }
}

export interface IAboutViewModel {
    about: AboutModel;
}

index.html

<script src="~/lib/systemjs/dist/system.src.js"></script>
<script src="~/lib/angular2/bundles/router.js"></script>
<script src="~/lib/angular2/bundles/http.js"></script>
<script src="~/lib/angular2/bundles/angular2-polyfills.js"></script>
<script src="~/lib/angular2/bundles/angular2.dev.js"></script>
<script src="~/lib/es6-shim/es6-shim.js"></script>
<script>
    System.config({
        packages: {
            app: {
                format: 'register',
                defaultExtension: 'js'
            },
            rxjs: {
                defaultExtension: 'js'
            }
        },
        map: {
            rxjs: "lib/rxjs"
        }
    });
    System.import('app/boot')
            .then(null, console.error.bind(console));
</script>
like image 636
Ryan Langton Avatar asked Jan 12 '16 02:01

Ryan Langton


People also ask

How do you fix undefined properties Cannot be read?

To solve the "Cannot read properties of undefined" error, make sure that the DOM element you are accessing exists. The error is often thrown when trying to access a property at a non-existent index after using the getElementsByClassName() method. Copied! const boxes = document.

What does Cannot read property of undefined mean?

What Causes TypeError: Cannot Read Property of Undefined. Undefined means that a variable has been declared but has not been assigned a value. In JavaScript, properties and functions can only belong to objects.

Can not read properties of undefined angular?

To fix Error: TypeError: Cannot read property '…' of undefined with Angular, we should make sure the variable we're rendering is defined. to use the safe navigation operator if foo and bar are objects. We use arr && arr. length > 0 to make sure arr is defined and it has length bigger than 0.

What is the meaning of Cannot Read property Code of null?

The "Cannot read property 'value' of null" error occurs when: trying to access the value property on a null value, e.g. after calling getElementById with an invalid identifier. inserting the JS script tag before the DOM elements have been declared.


3 Answers

Please include your view and model next time (app/about/about.html and about.model).

If you are returning an array, you can use the asyncPipe, which "subscribes to an Observable or Promise and returns the latest value it has emitted. When a new value is emitted, the async pipe marks the component to be checked for changes" hence the view will be updated with the new value.

If you are returning a primitive type (string, number, boolean) you can also use the asyncPipe.

If you are returning an object, I'm not aware of any way to use asyncPipe, we could use the async pipe, in conjunction with the safe navigation operator ?. as follows:

{{(objectData$ | async)?.name}}

But that looks a bit complicated, and we'd have to repeat that for each object property we wanted to display.

As @pixelbits mentioned in a comment, you can subscribe() to the observable in the controller and store the contained object into a component property. Then use the safe navigation operator or NgIf in the template:

service.ts

import {Injectable} from 'angular2/core';
import {Http} from 'angular2/http';
import 'rxjs/add/operator/map';  // we need to import this now

@Injectable()
export class MyService {
  constructor(private _http:Http) {}
  getArrayData() {
    return this._http.get('./data/array.json')
      .map(data => data.json());
  }
  getPrimitiveData() {
    return this._http.get('./data/primitive.txt')
      .map(data => data.text());   // note .text() here
  }
  getObjectData() {
    return this._http.get('./data/object.json')
      .map(data => data.json());
  }
}

app.ts

@Component({
  selector: 'my-app',
  template: `
    <div>array data using '| async':
      <div *ngFor="let item of arrayData$ | async">{{item}}</div>
    </div>
    <div>primitive data using '| async': {{primitiveData$ | async}}</div>
    <div>object data using .?: {{objectData?.name}}</div>
    <div *ngIf="objectData">object data using NgIf: {{objectData.name}}</div>`
  providers: [HTTP_PROVIDERS, MyService]
})
export class AppComponent {
  constructor(private _myService:MyService) {}
  ngOnInit() {
    this.arrayData$     = this._myService.getArrayData();
    this.primitiveData$ = this._myService.getPrimitiveData();
    this._myService.getObjectData()
      .subscribe(data => this.objectData = data);
  }
}

data/array.json

[ 1,2,3 ]

data/primitive.json

Greetings SO friends!

data/object.json

{ "name": "Mark" }

Output:

array data using '| async':
1
2
3
primitive data using '| async': Greetings SO friends!
object data using .?: Mark
object data using NgIf: Mark

Plunker

like image 71
Mark Rajcok Avatar answered Oct 08 '22 22:10

Mark Rajcok


It looks like you have referred to about.title in the view about.html but the about variable is instantiated only after the http request is completed. To avoid this error you can wrap about.html with <div *ngIf="about"> ... </div>

like image 37
TheKojuEffect Avatar answered Oct 08 '22 22:10

TheKojuEffect


The previous answer is correct. You need to check if the variable is defined before use it in your template. Using HTTP request it need time to define it. use *ngIf to check. Example is provided from angular with https://angular.io/docs/ts/latest/tutorial/toh-pt5.html and example is http://plnkr.co/edit/?p=preview

<div *ngIf="hero">
  <h2>{{hero.name}} details!</h2>
<div>

You can check app/hero-detail.component [ts and html]

like image 19
Zlatko Yankov Avatar answered Oct 08 '22 22:10

Zlatko Yankov