Vladyn Vladyn - 17 days ago 6
Javascript Question

Can't bind JSON results to Component variable

I'm trying a simple component that has to pull data from a JSON file. I'm almost copying the functionality from generated Fountain App, but for some reason I can't get the desired results. I have a component like:

import {Component, Inject} from "@angular/core";
import {Http} from '@angular/http';

@Component({
selector: 'body-selector',
template: require('./body.html')
})

@Inject(Http)

export class BodyComponent {
constructor(http: Http) {
this.http = http;
this.getText('app/texts/main.json').subscribe(result => {
console.log(result);
this.texts = result;
});
console.log(this.texts);
}
getText(url) {
return this.http.get(url).map(response => response.json());
}
}


on the first
console.log
I have
[Object object] [Object object] [Object object]
, which is correct as I have three entries in the JSON. On the second however I've got
undefined
, which turns into an error in the browser.

Error in ./BodyComponent class BodyComponent - inline template:3:6 caused by: Cannot read property 'title' of undefined


I'm looking at the example generated from the fountain app, but I can't get what I'm doing wrong.

Answer

You have multiple problems:

  • The first console.log is inside the callback, where this.texts has just been set. However the second one is outside the callback, so it won't have been. Therefore you'll always see undefined for that, because...

  • ...you never set a default value for this.texts, and your template apparently doesn't have any e.g. *ngIf to handle it being null or undefined, causing errors prior to the callback being called.

Below is your code, refactored to start with an empty this.texts (assuming it should be an array; please adapt to taste) and simplifying the injection of Http. Also note the comments, and that I've used templateUrl to avoid the require and OnInit to trigger the HTTP call slightly later in the component lifecycle rather than doing it in the constructor.

import {Component, OnInit} from '@angular/core';  // note consistent quotes
import {Http} from '@angular/http';

@Component({
  selector: 'body-selector',
  templateUrl: './body.html',
})
export class BodyComponent implements OnInit {
  texts: any[] = [];  // start with an empty array

  constructor(private http: Http) { }  // inject Http here, no need to assign to this

  ngOnInit() {
    this.http
      .get('app/texts/main.json')
      .map(response => response.json())
      .subscribe(result => {
        console.log(result);  // only log *inside* the callback
        this.texts = result;
      });
    // outside the callback, the HTTP call hasn't yet finished
  }

}

You could also solve this by having an ngIf in your HTML to prevent the element from being loaded before the data is.

<div class="main-container" *ngIf="texts">
  ...
</div>

I'd strongly recommend running through the basic Angular 2 tutorials to get on top of this stuff, see e.g. the Tour of Heroes.

Comments