2

I am migrating some code from Angular1 to Angular2 and having a few issues. I can open a json response, populate a template, and access data from template ng functions but have not been able to access the data directly from the component class. From what I have read and the error messages seen Angular2 http / observable does not seem to return a pure json object, so I suspect I need to remap it, but not sure how. I believe it should also be possible to drop back to promises using onPromise but have not managed to get that working. I have spent a lot of time googling for solutions, and have tried to implement most of them, but no luck. If anyone can advise on how to remap the response to a usable format or directly access data in the response it would be greatly appreciated.

Example http call from the service :-

getExam() {
    return this._http.get('/json/exam.json')
      .map(data => data.json());
  }

Example subscribe :-

  ngOnInit() {
      this._examsService.getExam()
        .subscribe(response => this.exam = response);
    console.log(this.exam.length);  //this fails
  }

Example console log error :-

TypeError: Cannot read property 'length' of undefined in [null]

Example data structure (very simplified for testing) :-

{"title":"My Practice Exam",
  "questions":[
    {"question":"1+1 = ",
      "answers":[
        {"answer":"2","correct":"Y","selected":"N","iscorrect":""},
        {"answer":"5","correct":"N","selected":"N","iscorrect":""}]},
    {"question":"2+2 = ",
      "answers":[
        {"answer":"4","correct":"Y","selected":"N","iscorrect":""},
        {"answer":"7","correct":"N","selected":"N","iscorrect":""}]},
    {"question":"3+3 = ",
      "answers":[
        {"answer":"6","correct":"Y","selected":"N","iscorrect":""},
        {"answer":"8","correct":"N","selected":"N","iscorrect":""}]}]}

In Angular1 I was able to directly access data from functions - e.g as follows, and would like to do similar in Angular2

if ($scope.myExams[0].questions[q].answers[a].correct == 'y') ...
Günter Zöchbauer
  • 623,577
  • 216
  • 2,003
  • 1,567
Robert
  • 533
  • 2
  • 7
  • 13
  • Seems that my console.log debug did not work from ngOnInit but I am able to access the full JSOn structure from a function :- getTitle() { console.log(this.exam.questions[1].answers[1].correct); } – Robert Jan 21 '16 at 14:25

2 Answers2

4

With this code

ngOnInit() {
  this._examsService.getExam()
    .subscribe(response => this.exam = response);
  console.log(this.exam.length);  //this fails
}

the first line sends the request this._examsService.getExam() .subscribe(...) and registers interest in the response, then console.log(this.exam.length) is executed, but at this time respone => this.exam = response hasn't been execute yet, because getExam() is not yet done connecting to the server and receiving the response.

You need to stay in the chain of events to work with the data that is returned eventually, like:

ngOnInit() {
  this._examsService.getExam()
    .subscribe(response => {
      this.exam = response;
      console.log(this.exam.length);  //this shoudn't fail anymore
    });
}

I don't know if this solves your problem but your question doesn't provide enough information about your requirements for a more elaborate solution.

Günter Zöchbauer
  • 623,577
  • 216
  • 2,003
  • 1,567
  • 1
    This version of ngOnInit does complete without error, but the console.log output only shows "undefined" rather than the length of the returned object. – Robert Jan 22 '16 at 00:41
  • Then `getExam()` doesn't return a value. Can you add the code of `getExam()` to your question? – Günter Zöchbauer Jan 22 '16 at 04:55
  • 1
    The "undefined" issue above was because the length property only exists on JSON arrays, and is undefined on JSON objects. I am able to return this.exam.questions.length but this.exam.length returns "undefined". So ... basically this was due to my misunderstanding of when the length property could be used. I very much appreciate your help, as it did force me to revisit my assumptions and test a few more combinations. – Robert Jan 22 '16 at 05:51
  • I see, thanks for the feedback. I don't know these details because I don't use TS/JS myself, I use only Dart. You could post an answer with this information. – Günter Zöchbauer Jan 22 '16 at 05:55
1

I think that the following case is the normal behavior:

ngOnInit() {
  this._examsService.getExam()
    .subscribe(response => this.exam = response);
  console.log(this.exam.length);  //this fails
}

because you try to access the length property on the exam object that will set later and when the response will be there (in the subscribe method).

That said, when an error is thrown within the observable, the map operator isn't called. If you want to transform the error response, you can leverage the catch operator, as described below:

this._examsService.getExam()
    .subscribe(
      // Success
      response => this.exam = response,
      // Failure
      response => {
        // Do something
      });

and the corresponding service code:

getExam() {
  return this.http.get('http://...')
           .map(res = > res.json())
           .catch(res => {
             // If you want to extract the JSON error
             // message from the response
             return Observable.throw(res.json());
           });
}

Otherwise you can also leverage the async pipe to directly set the observable on the component and not do the subscribe:

this.exam = this._examsService.getExam();

and in the associated template

<ul>
  <li *ngFor="#e of exam | async">{{e.name}}</li>
</ul>

Hope it helps you, Thierry

Günter Zöchbauer
  • 623,577
  • 216
  • 2,003
  • 1,567
Thierry Templier
  • 198,364
  • 44
  • 396
  • 360
  • Thanks, appreciate the reply. it does seem that ngOnInit is the wrong place to debug. I moved the debug out to a function and was able to call it successfully from a click, so am able to access the full JSON tree. – Robert Jan 21 '16 at 14:22
  • Pleased to hear that ;-) In fact, this isn't linked to the component lifecycle (ngOnInit is a lifecycle hook) but to the observable... – Thierry Templier Jan 21 '16 at 14:30