Angular 6 sort JSON from observable by key - javascript

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();
}),
);

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

Format httpclient response for *ngFor?

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

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;

Property 'campanha' does not exist on type 'DisputaComponent[]' - Angular 2

I'm trying to access a property inside an object called disputas but I'm getting this message:
[ts] Property 'campanha' does not exist on type 'DisputaComponent[]'
I can't access any property inside disputas, I think it's because its returning an Array of disputas, so how can I access each object disputa inside this array?
What I'm trying to do is show only objects with the same ID of the page, here's the snippet code:
constructor(service: DisputaService, private route:ActivatedRoute,
private router:Router, private campanha_service:FiltroNegociacaoService){
service
.lista()
.subscribe(disputas => {
if (this.disputas.campanha.cliente_id == this.campanha.cliente_id) // this is where I get the message
this.disputas = disputas;
console.log("Disputas: ", disputas);
console.log("Campanha: ", this.campanha);
}, erro => console.log("erro"))
}
and here's the full code if you guys need it:
import { Component, OnInit } from '#angular/core';
import {DisputaService} from '../services/disputas.service';
import {FiltroNegociacaoComponent} from '../../../filtra-negociacao/components/filtra-negociacao.component';
import {FiltroNegociacaoService} from '../../../filtra-negociacao/services/filtro-negociacao.service';
import {ActivatedRoute, Routes, RouterModule, Router} from '#angular/router';
#Component({
moduleId: module.id,
selector: 'disputas',
templateUrl: `disputas.component.html`,
providers: [DisputaService, FiltroNegociacaoService]
})
export class DisputaComponent implements OnInit {
public disputas:DisputaComponent[] = [];
public loading = false;
campanhas: FiltroNegociacaoComponent;
campanha:any;
service: DisputaService;
name: string;
proposta_inicial:number;
propostas_realizadas:number = 0;
maximo_propostas:number;
status = {
status_nome: ""
}
id:number;
constructor(service: DisputaService, private route:ActivatedRoute,
private router:Router, private campanha_service:FiltroNegociacaoService){
service
.lista()
.subscribe(disputas => {
if (this.disputas.campanha.cliente_id == this.campanha.cliente_id)
this.disputas = disputas;
console.log("Disputas: ", disputas);
console.log("Campanha: ", this.campanha);
}, erro => console.log("erro"))
}
ngOnInit():void{
this.route.params.subscribe(params =>{
let id = params['id'];
this.campanha_service
.buscaPorId(id)
.subscribe(campanha => {
this.campanha = campanha;
},
erro => console.log(erro));
})
}
Thanks in advance :)
You are retrieving an array of disputas and trying to find the ones with the same cliente_id as the one in this.campanha. The array itself does not have this property, you should filter the array, and then set the result:
.subscribe((disputas: DisputaComponent[]) => {
this.disputas = disputas.filter(
disputa => disputa.campanha.client_id === this.campanha.cliente_id
);
}

can't access a returned object properties from a service by angular 2 data binding

Process
I am using a service to get data (objects) from a json file with an observable and display them in the HTML template.
Problem
I can't access the objects properties by using {{obj.prop}}, it throws an error "Cannot read property 'prop' of undefined".
However if I try to access it in the component, it works.
Code
ContentService
import { Injectable } from '#angular/core';
import { Http, Response } from '#angular/http';
import { Observable } from 'rxjs/Rx';
import 'rxjs/Rx';
#Injectable()
export class ComponentContentService {
constructor(private _http: Http) { }
getContent() {
return this._http
.get('./app/services/dataContent.json')
.map((response:Response) => response.json())
.do(response => console.log('response = ', response))
}
}
TopContentComponent
import { Component } from '#angular/core';
import { WowComponent } from '../libraries.components/wow.component/wow.component';
import { BackstretchComponent } from '../libraries.components/backstretch.component/jquery.backstretch.component';
import { ComponentContentService } from '../services/component.content.service';
#Component({
selector: 'top-content',
templateUrl: './app/top-content.component/top-content.component.html',
directives: [WowComponent, BackstretchComponent]
})
export class TopContentComponent {
header : any;
description : any;
data : any;
constructor(private _ComponentContentService: ComponentContentService) {}
ngOnInit() {this.getComponentContent();}
getComponentContent() {
this._ComponentContentService.getContent()
.subscribe(
(data) => {
this.data = data;
}
);
}
}
Template
<p>{{data.header.title}}<p>
JSON
{
"header" : {
"title":"Our New Course is Ready",
"description" : "We have been working very hard"
},
"Footer" : {
"title":"Our New Course is Ready",
"description" : "We have been working very hard to create the new version of our course. It comes with a lot of new features, easy to follow videos and images. Check it out now!"
},
}
You should change {{data.header.title}} for {{data?.header?.title}}

Categories

Resources