How to access nested json content in Ionic-3 using typescript


#1

I have a nested json returned from an http request

{
  "id": 1,
  "title": "Tug of war Competition",
  "description": "All kerala tug of war chamionship will be held at Kochi , which is organized by YMCA Kochi",
  "date": "2017-07-20T12:24:06.887Z",
  "event_type": "100 INR- 250 INR",
  "street": "Panamapillinagar",
  "city": "Kochi",
  "state": "Kerala",
  "country": "India",
  "latitude": 9.9588628,
  "longitude": 76.2956985,
  "contact_details": [
    {
      "id": 1,
      "primary_number": "99975655532",
      "email": "varun@gmail.com",
      "secondary_number": "978645321"
    }
  ],
  "photos": [],
  "category": {
    "id": 1,
    "name": "Sports"
  }
}

In my event-detail.ts file

import { Component } from '@angular/core';
import { IonicPage, NavController, NavParams, App } from 'ionic-angular';
import {HappeningService} from '../../providers/event-service';
import {AuthService} from '../../providers/auth-service';

@IonicPage()
@Component({
  selector: 'page-event-detail',
  templateUrl: 'event-detail.html',
  providers: [HappeningService, AuthService]
})
export class EventDetailPage {
  public eventId: any;
  public event = {};

  constructor(public navCtrl: NavController, private app: App, public authService: AuthService, public navParams: NavParams, public eventService: HappeningService) {
    this.eventId = navParams.get('eventId');
    
  }

      ionViewDidLoad() {
        
 
        //Check if already authenticated
        this.authService.checkAuthentication().then((res) => {
            console.log("Already authorized");
            this.loadEventDetail(this.eventId);
        }, (err) => {
            console.log("Not already authorized");
            this.app.getRootNav().setRoot("Login");
        });
 
    }


  loadEventDetail(eventId){
    this.eventService.happeningDetails(eventId)
    .then(data => {
      this.event = data;
      console.log(this.event);   
    });
  }

}

I am able to access the data inside the event variable in the html file like

{{ event.tilte}}
{{ event.description}}

But in the case of nested objects I am not able to retrieve data from inner components

{{ event.contact_details.primary_number}}
{{ event.category.name }}

It’s showing undefined property primary number in the console
Any help will be appreciated.I am new to typescript and not able to identify a solution for this


#2

Can you please have a look at this?


#3

Your template would get really messy really fast if you did it that way. My suggestion is to use ES6 getters. For example:


get name() {
   if (!event || !event.category || !event.category.name) return ''   
   else return event.category.name;
}

and then use name in your template instead of {{ event.category.name }}


#4

@AaronSterling Thanks for the suggestion, But my problem is that not able to access the category or contact_details array inside the json. Simply saying event.contact_details.primary_number is not working


#5

Are you sure it has arrived when you try to access it? I don’t see any asynchronous code in what you posted.


#6

Yes i am, look at the screenshot below, i have print the same in console

Also this provider is used to fetch data


  happeningDetails(eventId) {
    return new Promise((resolve, reject) => {
      this.storage.ready().then(() => {
        this.storage.get('token').then((value) => {
          this._token = value;
          let headers = new Headers({ 'Content-Type': 'application/json' });
          headers.append('X-USER-TOKEN', this._token);
          let options = new RequestOptions({ headers: headers });
          if (!this.data) {
            this._http.get(this._url + '/events/' + eventId, options)
            .subscribe(res => {
 
              this.data = res.json();
              resolve(this.data);
              console.log(this.data);
              resolve(res.json());
            }, (err) => {
              reject(err);
            });
          }
        });
      });
    });
  }

#7

And JSON.parse() drops properties when you try to turn the file into an object? Maybe I don’t understand where the issue is.


#8

Randomly calling people out in threads they are not participating in is rude.


#9

Sorry for the trouble.


#10

Correct me if i miss something important here… but your contact_details is an array
So you have to get the first element…

{{ event.contact_details[0].primary_number}}

If you have more contact_details you need to loop over the array with *ngFor=...


#11

Presumably the access token is constant, especially because you’re putting it in an object property. If so, it should not be fetched from storage on every call to happeningDetails() (which is way too convoluted).

class HappeningService {
  private _token: Promise<string>;
  constructor(storage: Storage, private _http: Http) {
    this._token = storage.ready().then(() => storage.get('token'));
  }

  private _authedOptions(): Promise<Headers> {
    return this._token.then((token) => {
      let headers = new Headers();
      headers.append('X-USER-TOKEN', token);
      return new RequestOptions({headers: headers});
    });
  }

  happeningDetails(eventId: string): Promise<Happening> {
    return this._authedOptions().then((opts) => {
      return this._http.get(this._url + '/events/' + eventId, opts).toPromise();
    });
  }
}

#12

+1 for your response I was thinking the same


#13

@dominikstrasser I already tried that and following was the result.I am not sure what is the exact reason behind this


#14

This looks cool and elegant.Thanks for the comment


#15

ah ok…
so i think the following is the case here:

you fetch some data -> so it’s asynch…
you try to display {{ event.contact_details[0].primary_number}}
but when the app renders the first time your event is still an empty object (public event = {};)
so event.contact_details is undefined

you may want to do the following:
initialize your event as null;
public event = null;
and in your template you have something like->

<div *ngIf="event">
   {{ event.contact_details[0].primary_number }}
</div>

so in this case your template renders only when your data is ready.

or you just wrap the contact details in an ngIf

<div *ngIf="event && event.contact_details && && event.contact_details[0]">
   {{ event.contact_details[0].primary_number }}
</div>

#16

@dominikstrasser Thanks a lot man. That was the issue.Already spend hours on this issue.


#17

While @dominikstrasser’s suggestion is perfectly workable, I feel stylistically that it foists too much work onto the template. The controller should be responsible for presenting the data in a form that the template can always safely consume.

So, I would prefer initializing a dummy object over guarding against things in the template.

event = { contact_details: [] };

#18

i agree with you on the part that there should not be too much logic in the templates.
but there is no guarantee that there are contact_details in the response. so i would not initialize it empty… this may lead to unwanted side effects.
so you may want to do something like this

{{ getPrimaryNumber(event) }}

public getPrimaryNumber(event) {
if(!event || !event.contact_details || !event.contact_details[0]) return;
return event.contact_details[0].primary_number
}

#19

I guess that depends on the backend API contract. In this case, it seems like a relatively safe assumption that the system would not allow creation of an event that has no contact information, but you may be right.


#20

I agree with @dominikstrasser , that sometimes there are chances of getting empty contact details in the response. Thanks for the clarification @rapropos