Reputation: 185
How do you map and use a JSON reponse that is a single object, rather than an array?
Recently, I started adding a new feature to a project I'm working on that should be taking a JSON response from an API and filling out a simple template with data from it. Shouldn't be difficult, right? Well, no... and yet, yes...
Mock version of the JSON response:
{
"id": 1,
"name": "Acaeris",
}
profile.service.ts
import { Injectable } from '@angular/core';
import { Http, Response } from '@angular/http';
import { Observable } from 'rxjs/Observable';
import { Profile } from './profile';
/**
* This class provides the Profile service with methods to read profile data
*/
@Injectable()
export class ProfileService {
/**
* Creates a new ProfileService with the injected Http.
* @param {Http} http - The injected Http.
* @constructor
*/
constructor(private http: Http) {}
/**
* Returns an Observable for the HTTP GET request for the JSON resource.
* @return {Profile} The Observable for the HTTP request.
*/
get(): Observable<Profile> {
return this.http.get('assets/profile.json')
.map(res => <Profile>res.json())
.catch(this.handleError);
}
/**
* Handle HTTP error
*/
private handleError (error: any) {
let errMsg = (error.message) ? error.message :
error.status ? `${error.status} - ${error.statusText}` : 'Server error';
console.error(errMsg);
return Observable.throw(errMsg);
}
}
profile.component.ts
import { Component, OnInit } from '@angular/core';
import { ProfileService } from '../services/profile/profile.service';
import { Profile } from '../services/profile/profile';
/**
* This class represents the lazy loaded ProfileComponent
*/
@Component({
moduleId: module.id,
selector: 'sd-profile',
templateUrl: 'profile.component.html',
styleUrls: ['profile.component.css'],
})
export class ProfileComponent implements OnInit {
errorMessage: string;
profile: Profile;
/**
* Creates an instance of the ProfileComponent with the injected
* ProfileService
*
* @param {ProfileService} profileService - The injected ProfileService
*/
constructor(public profileService: ProfileService) {}
/**
* Get the profile data
*/
ngOnInit() {
this.getProfile();
}
/**
* Handles the profileService observable
*/
getProfile() {
this.profileService.get()
.subscribe(
data => this.profile = data,
error => this.errorMessage = <any>error
);
}
}
profile.ts
export interface Profile {
id: number;
name: string;
}
And I'm just trying to output it using {{profile.name}}
but this ends up with the console showing a whole load of error messages and no output. If I try to check the contents of profile
after it has loaded, it tells me it is undefined
.
However, here's the confusing part. If I replace all the Profile
references to Profile[]
, wrap the JSON in an array, add *ngFor="let p of profile"
abd use {{p.name}}
everything works fine. Unfortunately, in the actual finished application I would not have control of the JSON format. So what am I doing wrong when trying to handle it as a single object in comparison to handling as an array of objects?
Upvotes: 1
Views: 718
Reputation: 6825
Looks like at expression {{profile.name}} profile variable is undefined at page rendering moment. You can try either add some getter like this:
get profileName(): string { return this.profile ? this.profile.name ? ''; }
and use at template {{profileName}} or you can use ngIf at template like this:
<div *ngIf="profile">{{profile.name}}</div>
or shorter (as drewmoore suggested at comment below):
<div>{{profile?.name}}</div>
When you are working with array it is the same situation - at first rendering time array is undefined. ngFor handles this for you and renders nothing. When async operation of getting 'profile items' is complete - UI is rerendered again with correct values.
Upvotes: 6
Reputation: 2317
The map
function returns Observables which are a collection of elements. It basically work the same way as the map
function for arrays.
Now to solve you can replace the Profile
references by Profile[]
and use {{profile[0].name}}
.
Upvotes: -1