Format httpclient response for *ngFor? - javascript

Hi I was wondering if anyone could help me solve a small problem.
I am received data from my rest api which is returned as an array with objects inside.
Once I get it to my service I try to transform the data and push it to a subject so that it can inform my component that the data is here or updated.
When i console.log the data I get
0:{code: "AUH", name: "Abu Dhabi"}
1:{code: "ALY", name: "Alexandria"}
2:{code: "LTS", name: "Altus"}
3:{code: "ANK", name: "Ankara"}
4:{code: "AIY", name: "Atlantic City"}
5:{code: "BAK", name: "Baku"}
6:{code: "BKK", name: "Bangkok"}
7:{code: "EAP", name: "Basel"}
8:{code: "BJS", name: "Beijing"}
So when I try and use my *ngFor I get [object]p[Object]
How can I format this to work with *ngFor?
city-list.component.html
import { CityService } from "./services/city-list.service";
import { Component, OnInit, OnDestroy } from "#angular/core";
import { City } from "../cities/models/city";
import { Subscription } from "rxjs";
#Component({
selector: "<app-cities></app-cities>",
templateUrl: "./city-list.component.html"
})
export class CityListComponent implements OnInit, OnDestroy {
cities: City[];
private citiesSub: Subscription; // so as to unsubscribe if page changes/ memory leak
constructor(public cityService: CityService) {}
ngOnInit() {
this.cityService.getCities();
this.citiesSub = this.cityService
.getCityUpdateListener()
.subscribe((cities) => {
this.cities = cities;
});
// 1st value: when data emit 2nd value: error emit, 3rd value function for when no more data is available
}
ngOnDestroy() {
this.citiesSub.unsubscribe();
}
}
// subject is an observable but you can call next on them to emit a change when you want
"service"
import { Subject } from 'rxjs';
import {Injectable} from '#angular/core';
import {HttpClient} from '#angular/common/http';
import { map } from "rxjs/operators";
import {City} from '../models/city';
#Injectable()
export class CityService {
cities: City[] = [];
private updatedCities = new Subject<City[]>();
constructor(private http: HttpClient) {}
getCities() {
this.http.get<{message: string; cities: City[]}>('http://localhost:3000/cities')
.pipe(
map((cityData)=>{
return cityData.cities.map(city=>{
return{
code: city.code,
name: city.name
};
});
})
)
.subscribe((transCity) => {
this.cities = transCity;
console.log(this.cities);
this.updatedCities.next([...this.cities]);
});
}
getCityUpdateListener() {
return this.updatedCities.asObservable();
}
}

You can just use the json pipe:
<div *ngFor="let item of response">{{ item | json }}</div>
If you want to display it in "pretty" instead of as json, you need to access the individual fields of the item and format it in the desired way.

try as below , first get keys form reponse object you are receiving from http call and then go through each key in html , might resole your issue
in ts file
//response is data you received after making http call, list of cities in your case
keys = Object.keys(response);
in html file
<div *ngFor="let key of keys">
{{response[key].code }} {{response[key].name }}
</div>
this should work based on response you are getting from server

It looks like the issue here is that you're not actually returning an array of City, instead you're returning a dictionary or Map<City>. You'll probably want to iterate over your response and map it to the correct type.
this.citiesSub = this.cityService
.getCityUpdateListener()
.subscribe((cityMap) => {
this.cities = [ ...cityMap.values() ]
});

Asuming you are using httpClient(new released in angular5) then there is no need of the map() and pipe() functions, results are mapped to json by default you just have to subscribe to the service
this is how it would look your new service class
import { Subject } from 'rxjs';
import {Injectable} from '#angular/core';
import {HttpClient} from '#angular/common/http';
import { map } from "rxjs/operators";
import {City} from '../models/city';
#Injectable()
export class CityService {
cities: City[] = [];
private updatedCities = new Subject<City[]>();
constructor(private http: HttpClient) {}
getCities() {
return this.http.get<City[]>('http://localhost:3000/cities')//http.get<any> also work but for type safety i am asuming City[] array have the same structure.
}
getCityUpdateListener() {
return this.updatedCities.asObservable();
}
}
Then in your component you would have to subscrive to that service and use it
constructor(public cityService: CityService) {
this.cityService.getCities().subscribe(cities => {
this.cities = cities;
console.log(cities);
}, error=> {console.log(error)});//handling errors
}
ngOnInit() { } // just moved the service call to the constructor of the component
I hope this solve your problem,
Thanks

Related

Can't get deeper into the response data object in subscribe's callback function. Why?

I'm fetching data from RandomUser api with Angular HttpClient. I've created a method in a service calling GET, mapping and returning a Observable. Then I subscribe on this method in a component importing this service and in subscribe's callback I am trying to store the response data in a local variable. The problem is I can't get "deeper" into this response object than:
this.randomUser.getNew().subscribe(data => {
this.userData = data[0];
})
If I'm trying to reach any further element of that response object, and log it to console it I get "undefined". To be precise I cant reference to, for example:
this.randomUser.getNew().subscribe(data => {
this.userData = data[0].name.first;
})
If I store the "data[0]" in a variable first I can get into these unreachable properties. What is the reason of it? Please, help. Let me know what important piece of fundamental JS (or Angular) knowledge I'm not aware of. As far as I know I should be able to do what I am trying to do :)
service looks like these
import { Injectable } from '#angular/core';
import { HttpClient } from '#angular/common/http';
import { map } from 'rxjs/operators';
import { Observable } from 'rxjs';
#Injectable({
providedIn: 'root'
})
export class RandomUserService {
url: string = " https://randomuser.me/api/ "
constructor(private http: HttpClient) { }
public getNew(): Observable<any> {
return this.http.get(this.url)
.pipe(map(responseData => {
const returnDataArray = [];
for (const key in responseData) {
returnDataArray.push(responseData[key])
}
return returnDataArray;
}))
}
}
component looks like these:
import { Component, OnInit } from '#angular/core';
import { RandomUserService } from 'src/app/shared/random-user.service';
import { Observable } from 'rxjs';
#Component({
selector: 'app-single-character',
templateUrl: './single-character.component.html',
styleUrls: ['./single-character.component.scss']
})
export class SingleCharacterComponent implements OnInit {
userData: object;
fname: string;
constructor(private randomUser: RandomUserService) {
this.randomUser.getNew().subscribe(data => {
this.userData = data[0];
})
}
ngOnInit(): void {
}
}
You are not parsing the returned data correctly in getNew().
The returned data looks like this:
So you need to access the user data like:
this.randomUser.getNew().subscribe(data => {
this.userData = data[0][0]; // note 2nd [0]
})
or for first name:
this.randomUser.getNew().subscribe(data => {
this.userData = data[0][0].name.first;
})
See stackblitz here: https://stackblitz.com/edit/so-http-parse?file=src/app/app.component.ts

Casting firestore observables to custom objects

I'm new to angular and firestore and trying to figure out how to cast the data received from firebase directly to models. What is the best approach here?
Currently I get the data, but it looks like it's not casted into a Blimp object. When I try to call getImageUrl() on it in the view, I get the following error message.
ERROR TypeError: _v.context.$implicit.getImageUrl is not a function
So my question: What is the best and cleanest way to cast these observables to the correct local model? I was expecting the tags to cast it by default.
Current code
Custom model class
export class Blimp {
created_at: Date;
file_location: string;
id: string;
constructor() {
console.log('OBJ');
}
getImageUrl() {
return "https://*.com" + this.file_location;
}
}
Service class
import { Injectable } from '#angular/core';
import {Blimp} from '../models/blimp';
import { AngularFirestore } from '#angular/fire/firestore';
import {AngularFireStorage, AngularFireUploadTask} from '#angular/fire/storage';
import {Observable} from 'rxjs';
import {finalize} from 'rxjs/operators';
#Injectable({
providedIn: 'root'
})
export class BlimpService {
blimps: Observable<Blimp[]>;
constructor(private fireStore: AngularFirestore, private fireDisk: AngularFireStorage) { }
getBlimps() {
this.blimps = this.fireStore.collection<Blimp>('blimps').valueChanges();
return this.blimps;
}
}
Display component
import { Component, OnInit } from '#angular/core';
import {BlimpService} from '../../services/blimp.service';
import {Observable} from 'rxjs';
import {Blimp} from '../../models/blimp';
#Component({
selector: 'app-blimp-viewer',
templateUrl: './blimp-viewer.component.html',
styleUrls: ['./blimp-viewer.component.scss'],
})
export class BlimpViewerComponent implements OnInit {
blimps: Observable<Blimp[]>;
constructor(private blimpService: BlimpService) { }
ngOnInit() {
this.blimps = this.blimpService.getBlimps();
}
}
View
<ul>
<li *ngFor="let blimp of blimps | async">
{{ blimp | json}}
<img [src]="blimp.getImageUrl()" />
</li>
</ul>
Update #1
Changed the code to
I now have changed your example to: getBlimps() {
this.blimps = this.fireStore.collection<Blimp>('blimps')
.valueChanges()
pipe(map(b => {
let blimp = new Blimp();
blimp.created_at = b.created_at;
blimp.file_location = b.file_location;
blimp.id = b.id;
return blimp;
}));
return this.blimps;
}
This still complains in the view about the getImageUrl() not being found on the object.
# Solution
Looks like I forget a . (dot) in the last code
This code works:
this.blimps = this.fireStore.collection<Blimp>('blimps')
.valueChanges()
.pipe(map(collection => {
return collection.map(b => {
let blimp = new Blimp();
blimp.created_at = b.created_at;
blimp.file_location = b.file_location;
blimp.id = b.id;
return blimp;
});
}));
return this.blimps;
Concept :
You don't cast an observable to an object model. An observable is a stream which has a lifecycle.
An observable emits value to its subscribers, you need to subscribe to your observable to be notified when it emits value. You also need to close the subscription or the subscription will last until your observable complete causing memory leaks.
I can see you're using | asyncin your html template, it's a subscription handled by angular that auto-unsubscribe when needed.
Get data :
You need to map the data you received to a Blimp object, you can use map operator.
blimps$: Observable<Blimp[]>; // naming convention, suffix your observable with $
blimps$ = this.fireStore.collection<Blimp>('blimps')
.valueChanges()
.pipe(map(collection => {
return collection.map(b => {
let blimp = new Blimp();
blimp.created_at = b.created_at;
blimp.file_location = b.file_location;
blimp.id = b.id;
console.log(blimp);
console.log(b);
return blimp;
});
}));
return this.blimps;
As we changed blimps to blimps$, change your html template :
*ngFor="let blimp of blimps$ | async"
EDIT :
You can use your class constructor to initialize your object :
export class Blimp {
created_at?: Date;
file_location?: string;
id?: string;
constructor(blimp: Blimp = {}) {
this.created_at = blimp.created_at;
this.file_location = blimp.file_location;
this.id = blimp.id;
}
getImageUrl() {
return `https://*.com${this.file_location}`; // use string interpolation here
}
blimps$ = this.fireStore.collection<Blimp>('blimps')
.valueChanges()
.pipe(map(collection => {
return collection.map(b => new Blimp(b));
}));

Angular 6 sort JSON from observable by key

Note: I know how to sort a regular array of objects using .sort(), but this time I am stuck with an observable and I am not familiar with it.
I am fetching a JSON array of objects with a service:
import { Injectable } from '#angular/core';
import { HttpClient } from '#angular/common/http';
#Injectable()
export class MockDataService {
private dataUrl = 'assets/data';
constructor(private http: HttpClient) {}
get(filename: string) {
return this.http.get(`${this.dataUrl}/${filename}`);
}
}
With this service, we can just pass a filename of a json file and get an observable:
import { Component, OnInit } from '#angular/core';
import { MockDataService } from '../../shared/services/mock-data.service';
import { ObservableInput } from 'rxjs';
#Component({
selector: 'app-iconography',
templateUrl: './iconography.component.html'
})
export class IconographyComponent implements OnInit {
pbiMini$ = this.mockdata.get('pbi-mini-names.json');
constructor(private mockdata: MockDataService) {}
ngOnInit() {}
}
but now I need to sort this data by one of the object keys, e.g. "name"
{
"name": "Palette",
"code": "pbi-palette",
"char": ""
},
{
"name": "Shopping tag",
"code": "pbi-shopping-tag",
"char": ""
},
I have searched and I can't figure it out. In the past, when getting JSON as a plain array that's not an observable, I have successfully used
ngOnInit() {
this.pbiMini.sort((a, b) => a.name.localeCompare(b.name));
}
But it does not work on an observable like I have now. How is this done?
Update
Following a suggestion, I tried
ngOnInit() {
this.pbiMiniSorted$ = this.pbiMini$.pipe(
map(array => {
return array.sort();
})
);
}
but this fails to compile with the error:
error TS2339: Property 'sort' does not exist on type 'Object'.
Use map operator to modify the value in Observable.
const array$ = of([2, 1, 3]);
const modified$ = array$.pipe(
// map receives a function which receives Array and returns Array
map(array => {
// here you can access to the data as Array object
return array.sort();
}),
);

How to load this JSON data into Angular2

Im new to Angular2, I want to load this Json data and display in a page, Im not sure how to do..? From all sources I learnt I made a code and attached it below, But its not running because of some errors, can anyone help in fixing or writing a new code for me so that i can learn from it..
Thanks in advance for the help.
My code file - student.json
[
{
"name": "John",
"id_number": "12",
"attendance": "276 days",
"grade": "A"
},
],
this is my students.service.ts code
import {Injectable} from '#angular/core';
import { Http, Response } from '#angular/http';
#Injectable()
export class StudentsService {
constructor(private http:Http)
{
}
getStudents() {
return this.http.get('./students.json')
.map((response: Response) => {
console.log("mock data" + response.json());
return response.json();
}
}
and, this is my students.component.ts file
import {Component} from '#angular/core';
import { Http, Response } from '#angular/http';
import {StudentsService} from './students.service';
import 'rxjs/add/operator/map'
import 'rxjs/Rx';
#Component({
selector: 'students',
templateUrl: 'students.html',
styleUrls: ['./students.scss']
})
export class students {
public students;
constructor( private _studentsService:StudentsService, private http:Http)
{
this.students = this._studentsService.getStudents();
}
ngOnInit() {
this._load();
}
private _load() {
this.students = this._studentsService.getStudents();
}
}
You can write a service to load your html from json file and available all over your application like below.
#Injectable()
export class ConfigService {
public config: any;
private configObs: Observable<any>;
constructor(private http: Http) {
}
public load(filename: string): Observable<any> {
if ( this.config ) {
return Observable.of(this.config);
} else {
this.configObs = this.http.get(filename).map((res) => {
this.config = this.config || res.json() || {};
return this.config;
});
}
return this.configObs;
}
}
You can also put your data in typescript class format if that option is available referance answer
If you have JSON data and you want to show it in page.
Use Data Table to show It.
Here is the example you can see how to show on page.
Please click Here
Assign our json to a varible
myData = [{
"name": "John",
"id_number": "12",
"attendance": "276 days",
"grade": "A"
},
...
...
],
In your Html
<ul>
<li *ngFor="let data of myData">
<div>{{data.name}}</div>
<div>{{data.id_number}}</div>
<div>{{data.attendance}}</div>
<div>{{data.grade}}</div>
</li>
</ul>
Hope it helps
What you are dealing with is an Observable students, either you need to manually subscribe to that observable, or use the async pipe in the template which handles the subscribing for your.
Also you are now performing the request twice, in the constructor and in your OnInit. Remove one of those, I'd remove the one in the constructor, since I like to keep everything away from the constructor, that does not need to be there, like mentioned here: https://stackoverflow.com/a/35763811/6294072
Back to the subscribing... either do:
this.students = this._studentsService.getStudents();
<div *ngFor="let student of students | async">
<p>{{student.name}}</p>
<!-- ... -->
</div>
or:
this._studentsService.getStudents()
.subscribe(students => {
this.students = students;
})
<div *ngFor="let student of students">
<p>{{student.name}}</p>
<!-- ... -->
</div>

Angular 2 Interface throwing error of non existing property

I have an Angular 2 interface books.ts
export interface Books {
artists: Object;
tracks: Object;
}
This is the my service file where I am using it with http request searchService.ts
import { Injectable } from '#angular/core';
import { Http, Response, Headers, RequestOptions } from '#angular/http';
import { Observable } from 'rxjs/Observable';
import { Books } from 'app/pages/search-results/books';
import 'rxjs/add/operator/map'
#Injectable()
export class SearchService {
constructor(private _http:Http) { }
getBook(keyword): Observable<Books[]>{
return this._http.get('https://api.spotify.com/v1/search?q=' + keyword + '&type=track,artist')
.map((response: Response) => <Books[]> response.json());
}
}
And this is my component where I am using interface searchResults.ts
import { Component, OnInit } from '#angular/core';
import { ActivatedRoute, Router } from '#angular/router';
import { SearchService } from 'app/shared/search/search.service';
import { Books } from 'app/pages/search-results/books';
#Component({
selector: 'app-search-results',
templateUrl: './search-results.component.html',
styleUrls: ['./search-results.component.css'],
providers: [SearchService]
})
export class SearchResultsComponent implements OnInit {
keyword: any;
sub: any;
books: Books[];
errMessage: string;
arists: Object;
constructor(private _route: ActivatedRoute, private _router: Router, private _search: SearchService) { }
ngOnInit() {
this.sub = this._route
.queryParams
.subscribe(params => {
// Defaults to 0 if no query param provided.
this.keyword = params['keyword'] || 0;
this.getBooks(this.keyword);
});
//
}
getBooks(value) {
this._search.getBook(value)
.subscribe(
res => {
this.books = res;
console.log(res.artists);
},
error => { this.errMessage = <any>error }
);
}
}
The error comes when I try to console the res.artists. The error says Property 'artists' does not exist on type 'Books[]'. I am new to Angular 2 and doesn't know how to fix that.
The response is looks like
{artists:{limit: 20, item:[]}, tracks:{limit: 20, item:[]}}
I'm not sure but I think you try to get res.artist from collection of books. You can check it by for or e.g res[0].artist to get concrete artist.
getBook function in class SearchService return an array of Books object (Books[])
so, the res in getBooks function in SearchResultsComponent will be an Array of Books.
You can console.log(res) to see detail, if you want access to artists please try with res[0].artists if the res is not an empty array
The problem is that I am getting Object in response and I am assigning it to an Array which is causing the error. I have simply changes the both types to object and it solved my problem.
From this
books: Books[];
To this
books: Books;

Categories

Resources