Storing Objects inside Object in Arrays in Angular 2 - json

I'm trying to store this data, given from a Wordpress Backend with HTTP Get Request in Ionic 2 (Angular 2).
I'm receiving this data structure,
Console Log of data response-
I'm trying to store this data like the menus (menu_1 and menu_2) in array of menus, the categories in array of categories, dishes in array of dishes...
How can I do that?
I don't want to show or iterate using Pipes, I only want to storage in Arrays to work easier with them.
My code at the moment is like:
home.ts:
I have a injectable class (Globals) to call the http get, but I do the subscribe in the getMenus function on my home.ts component:
import { Component } from '#angular/core';
import { NavController } from 'ionic-angular';
import { Globals } from '../../providers/globals';
#Component({
selector: 'page-home',
providers: [Globals],
templateUrl: 'home.html'
})
export class HomePage {
menus: any;
constructor(public navCtrl: NavController, public globals: Globals) {
this.getMenus();
}
getMenus() {
this.globals.getMenus().subscribe(
data => {
console.log(data);
this.menus = data;
},
err => { console.log(err) }
);
}
}
And I have created a class, called Menu, at the moment is very simple:
import { Injectable } from '#angular/core';
import 'rxjs/add/operator/map';
#Injectable()
export class Menu {
name: any;
categories: any;
constructor() {
this.name = this.name;
this.categories = this.categories;
}
}
Where name is basic field of the object (key: name, value: "Today's menu" and categories is cat_1, cat_2 (two objects inside menu_1 object, which each contains more objects (dish_1, dish_2...).
My idea is create a class for every one of them, class Menu, class Category and class Dish. But I have any idea of how can I start store this objects in this classes. :S
Greetings!

The first thing to do is to create an interface for the data that you receive from the server, something like:
interface Dish {
Name: string;
Description: string;
Thumbnail: string;
}
interface Category {
[name: string]: Dish;
}
type ServerResponse = {
[name: string]: { [name: string]: Category; } & { name: string };
}
If you want to create classes from this data you can then:
class Menu {
name: string;
categories: { [name: string]: Category };
constructor(data: { [name: string]: Category; } & { name: string }) {
this.name = data.name;
this.categories = {};
Object.keys(data).forEach(name => {
if (name !== "name") {
this.categories[name] = new Category(data[name]);
}
});
}
}
(data: ServerResponse) => {
this.menus = {};
Object.keys(data).forEach(name => {
this.menus[name] = new Menu(data[name]);
});
}
You should also create the Category class and all, but that's the idea.

What are you trying to do ?
I think what you're trying to do is to normalize your data.
(Are you using a Redux pattern ? Maybe Ngrx ? If so, this is a great idea to normalize !)
Here's how a normalized state looks like : http://redux.js.org/docs/recipes/reducers/NormalizingStateShape.html
How should you do it ?
You can either do it by hand, which will become quite hard if you have many other requests to deal with, or you can describe your data in schema and use normalizr to do this job (normalizing data) for you.

If you don't know where to start. You can try this approach. First, create a model:
export class DummyModel {
menu: any;
cat: any;
dish: any;
...
//you can replace any with the type expected (string, number, etc)
}
In your component, you import your dummyModel and you set the data
import { DummyModel } from '../dummy.model';
/...
dummyModel: DummyModel = dummyData;
Also, consider #Nitzan Tomer advise, try to write your code and people here can help if you are facing an issue

Related

ANGULAR - Mapping nested JSON data from API

so I've been struggling for the past day or so with mapping the response from a mock API - I think I'm mapping it correctly but when I try to access the data it doesn't return anything in the HTML.
Please find my code below:
data.service.ts
import { HttpClient } from '#angular/common/http';
import { Injectable } from '#angular/core';
import { ConsentData, Prompt } from '#app/models/consent-data';
import { Observable } from 'rxjs';
#Injectable({
providedIn: 'root',
})
export class ConsentWebviewDataService {
constructor(private httpClient: HttpClient) {}
getConsentData(): Observable<ConsentData<Prompt>> {
return this.httpClient.get<ConsentData<Prompt>>(
'MY_API_URL',
);
}
}
data.ts (interface)
export interface ConsentData<Prompt> {
prompts: Prompt[];
}
export interface Prompt {
promptId: number;
headline: string;
body: string;
imageUrl: string;
consents: string[];
type: string;
}
app.component.ts
export class PromptComponent implements OnInit {
consentData: any;
constructor(private consentWebviewDataService: ConsentWebviewDataService) {}
ngOnInit(): void {
this.consentWebviewDataService.getConsentData().subscribe(data => {
this.consentData = data.prompts.map(consents => {
return {
promptId: consents.promptId,
headline: consents.headline,
body: consents.body,
imageUrl: consents.imageUrl,
consents: consents.consents,
type: consents.type,
};
});
});
}
}
Lastly here is the API JSON response:
{"prompts":[{"promptId":100,"headline":"Headline","body":"Body text.","imageUrl":"https://picsum.photos/200","consents":["Consent 1","Consent 2","Consent 3"],"type":"ConsentCampaign"}]}
From what I understand so far, after countless tutorials and resources, the getCosentData() function sends request to API, then in the component I subscribe to it, get the response, assign the response to the consentData variable, then map the contents of the response based on the interface / how the JSON response looks.
However, the problem is that I cannot access the mapped data in the HTML. I don't need it in a table, just need to get the mapped data.
I tried all variations such as {{ consentData.promptId }} which I mapped, and it returns ERROR TypeError: ctx.consentData is undefined. Tried {{ consents.promptId }} as well, etc. but nothing works.
What am I missing here? And apologies for the long question && thanks in advance for any help!
You mapped the response into a new array and trying to access it as an object
Try {{ consentData[0].promptId }} to get the id of first element

How to list objects in an object in html?

I'm writing app in Angular and Node.js. I have an object (order) that has a list of objects (items) that also contain objects (list of product id). I want to display them all in an html file. Please help me.
html file:
<div *ngIf="order">
<div *ngFor="let item of order.items"> // <- it does not work
<a>{{order.items}}</a> // <--
</div>
</div>
ts file:
export class AdminOrderItemComponent implements OnInit {
order: Order;
orderId;
constructor(private orderService: OrderService, private route: ActivatedRoute) { }
ngOnInit() {
this.route.paramMap
.subscribe(params => {
this.orderId = [params.get('id')];
this.getOrderById();
});
}
getOrderById() {
this.orderService.getOrderById(this.orderId).subscribe(
res => {
this.order = res;
},
err => console.log(err)
);
}
}
order interface:
export interface Order {
_id: any;
shipping: string;
userData: {};
sum: number;
items: {};
}
The ngFor iterates over the items of a collection. If you take a look at your model, you will realize that items is an object ({}), not an array ([]).
Your best bet is transforming the object received from your node.js backend to match your needs or (preferably I think) make your node.js model treat items as a collection as well which seems more appropriate.
Answer from #kg99:
Key: {{item.key}} and Value: {{item.value}}

Using service.ts variables on multiple components

I've set up next.service.ts with 3 variables (user, action, rest) and made setters(updateNext()) and getters (getUser, getAction, getRest). I've got to use the setter to change the variables in one component (stock-management component) and retrieved these variables in another component (inventory-record component) but I can't seem to retrieve them from another component (inventory-record-filled component).
I've tried returning a string ("TEST") in the getter and it worked, but when I tried returning a variable, it just returned nothing/empty string.
export class NextService {
private action: string;
private user: string;
private restraunt: string;
constructor() { }
updateNext(actions, users, restraunts) {
this.action = actions;
this.user = users;
this.restraunt = restraunts;
}
getAction(): string {
return this.action;
}
getUser(): string {
return this.user;
}
getRest(): string {
return this.restraunt;
}
export class InventoryRecordComponent implements OnInit {
name = '';
rest = '';
action = '';
constructor(private next: NextService) {
this.name = this.next.getUser();
this.action = this.next.getAction();
this.rest = this.next.getRest();
}
ngOnInit() {
document.getElementById('dne').style.display = 'none';
}
onSubmit(f: NgForm) {
const x = document.getElementById('dne');
if (!this.next.updateCode(this.code)) {
x.style.display = 'block';
f.resetForm();
} else {
this.next.updateCode(this.code);
location.replace('inventory-record/qty');
}
}
}
export class InventoryRecordFilledComponent implements OnInit {
name: string;
action: string;
rest: string;
constructor(private next: NextService) {
this.name = this.next.getUser();
this.action = this.next.getAction();
this.rest = this.next.getRest();
}
ngOnInit() {
}
}
Each component have its respective html files with {{ name }} {{ action }} {{ rest }}
If you need your component to behave as a Simpleton (where it contains the same values regardless of where in the application it is used) you must set its providedIn value to "root", like so:
import { Injectable } from '#angular/core';
#Injectable({
providedIn: 'root',
})
export class NextService {
// The rest of the code stays the same
}
The description for that can be found here: https://angular.io/guide/singleton-services#providing-a-singleton-service
If you don't do that, each component that imports NextService will have it's own instance of NextService, with its own isolated values. If you want the values of a service to be available everywhere that the service is used in, then you want the service to be a Simpleton, so you must follow the steps.
Following the steps above is not the only way to make your component a Simpleton, but as the link mentions, it is the preferred way to do that.
Hope that helps!

Loading data from a json file with an Interface

I'm writing a little ionic app for learning purposes and I would like to load data from a json file and assign it to an Interface that describes the data. But I'm struggling with getting it the right way:
import { Component } from "#angular/core";
import { HttpClient} from "#angular/common/http";
export interface PhonebookEntry {
name: string,
telephone: string,
description: string
}
#Component({
selector: 'page-phonebook',
templateUrl: 'phonebook.html'
})
export class PhonebookPage {
entries: Array<PhonebookEntry>;
constructor(public http: HttpClient) {
this.load_entries('assets/json/phonebook.json');
};
load_entries(filePath: string) {
return this.http.get(filePath)
.subscribe(
data => this.entries = data
);
};
}
I think only the line data => this.entries = data is wrong (also the IDE is telling me that), but I don't know to do this right and can't find documentation describing the correct way. If there actually is some I would be glad to know where I can find ressources about this.
subscribe return the response as an object, not as an array. So entries type should be changed.
entries: PhonebookEntry;
In the subscribe, need to assign a type for response data.
load_entries(filePath: string) {
return this.http.get(filePath)
.subscribe(
(data: PhonebookEntry) => this.entries = data // or use any type
);
};
Demo

Angular doesn't pass HTTP GET params properly

So I figuring out my way around Angular. Just started with a OpenWeather API based application using a simple GET method.
So here is my app.component.ts:
import { Component } from '#angular/core';
import { WeatherService } from './weather.service';
#Component({
selector: 'app-root',
templateUrl: './app.component.html',
styleUrls: ['./app.component.css'],
providers: [WeatherService]
})
export class AppComponent {
title = 'Ng-Weather';
cityName: string;
constructor(private weather: WeatherService) { }
search() {
this.weather.getWeatherbyName(this.cityName);
}
}
As you can guess, the cityName variable is two way binded. The search() function is invoked onclick of a button and the data is passed to the weatherservice. The contents of weather service is:
import { Injectable } from '#angular/core';
import { Http, Response, URLSearchParams } from '#angular/http';
import { Observable } from 'rxjs';
import { Weather } from './weather';
#Injectable()
export class WeatherService {
APIurl = "http://api.openweathermap.org/data/2.5/weather";
Appid = "xxx";
constructor(private Http: Http) { }
getWeatherbyName(name: string): Observable<any> {
let myParams = new URLSearchParams();
myParams.append('q', name);
myParams.append('appid', this.Appid);
// actual http request should look like this: http://api.openweathermap.org/data/2.5/weather?appid=xxx&q=Chennai
return this.Http.get(this.APIurl, { search: myParams})
.map(this.extractData)
.catch(this.handleError);
}
private extractData(res: Response) {
console.log(res.json());
let body = res.json();
return body.data;
}
private handleError(error: Response | any) {
console.error(error.message || error);
return Observable.throw(error.message || error);
}
}
But I get no error in my console or during the compile process. What is being done wrong? Also, how can I map the JSON I get to my class and give back that instance to the app.component?
Following is my class:
export class Weather {
city: String;
max_temp: String;
min_temp: String;
description: String;
}
And this is a sample JSON I receive:
{
"coord":{
"lon":80.28,
"lat":13.09
},
"weather":[
{
"id":803,
"main":"Clouds",
"description":"broken clouds",
"icon":"04n"
}
],
"base":"stations",
"main":{
"temp":304.15,
"pressure":1008,
"humidity":79,
"temp_min":304.15,
"temp_max":304.15
},
"visibility":6000,
"wind":{
"speed":3.1,
"deg":160
},
"clouds":{
"all":75
},
"dt":1504629000,
"sys":{
"type":1,
"id":7834,
"message":0.0029,
"country":"IN",
"sunrise":1504571272,
"sunset":1504615599
},
"id":1264527,
"name":"Chennai",
"cod":200
}
As you can see all I need is some data from the JSON and not the whole thing.
Your main problem here is that you are not subscribing to the observable that is being produced by your getWeatherbyName function. Observables returned by Http are cold:
Cold observables start running upon subscription, i.e., the observable sequence only starts pushing values to the observers when Subscribe is called. (…) This is different from hot observables such as mouse move events or stock tickers which are already producing values even before a subscription is active.
In order to subscribe to this observable, you can simply update your search function to the following:
search() {
this.weather.getWeatherbyName(this.cityName)
.subscribe();
}
This is by no means the complete solution to your problem - You will want to do something in the subscription, such as assign the information received to properties of your component so that they can be rendered in the UI.
You appear to have other issues in your linked project, but I suggest you ask separate questions on Stack Overflow if needed, or even better, your favorite search engine should be able to help.
Try passing a RequestOptions object to the http get instead:
import { RequestOptions } from '#angular/http';
getWeatherbyName(name: string): Observable<any> {
let myParams = new URLSearchParams();
myParams.append('q', name);
myParams.append('appid', this.Appid);
let options = new RequestOptions({ search: myParams}); //<----- NEW
// actual http request should look like this: http://api.openweathermap.org/data/2.5/weather?appid=xxx&q=Chennai
return this.Http.get(this.APIurl, options) //<<----- NEW
.map(this.extractData)
.catch(this.handleError);
}