Angular and JSON - json

I'm implementing a simple system to import JSON elements in Angular.
Everything works fine: I've used an interface, an observable and a directive. You can find the JSON here: http://jsonplaceholder.typicode.com/todos
Now, I want to use "completed", the boolean from JSON file, to display or not users when the page is loaded. There is a boolean "showUSer" and a method "displayUSer()" but I don't get it...
I cannot correctly retrieve this JSON data.
Any ideas ? :>
import { Component, OnInit } from '#angular/core';
import { HttpClient } from '#angular/common/http';
interface JSP {
"userId": string;
"id": string;
"title": string;
"completed": boolean
}
#Component({
selector: 'app-product',
template: `<div class="display" *ngFor="let todo of todos">
<div>User Id: {{todo.userId}}</div>
<div >id: {{todo.id}}</div>
<div *ngIf="showUser">Title: {{todo.title}}</div>
</div>`,
styles: ['.display {margin-top: 20px; margin-bottom: 20px;}']
})
export class ProductComponent implements OnInit {
title: string = "Products List";
todos: JSP[];
showUSer: boolean;
constructor(private http: HttpClient){
}
ngOnInit(){
this.http.get<JSP[]>('http://jsonplaceholder.typicode.com/todos')
.subscribe(result => this.todos = result);
}
displayUSer(): void {
this.showUSer = this.todos.completed;
}
}

Nitpicks: Your question says to display or not users but your code seems to be display or not the title. Also why do you capitalize the 'S' in 'USers'?
The problem is this function which seems to ignore your actual data layout:
displayUSer(): void {
this.showUSer = this.todos.completed;
}
todos is a property of your controller. This is an array from the api call you make and it doesn't contain a completed property, so this.todos.completed will always be false. I'm a little surprised that you don't get an error compiling your typescript.
It looks like you want this flag to be on a 'todo item' basis and not page-wide, so this.showUSer doesn't make sense. Also you don't seem to be calling displayUSer to set the value in any case.
Since you are looking at an individual todo item and the query is simple, why don't you just look at the flag?
<div *ngIf="todo.completed">Title: {{todo.title}}</div>
If you are wanting to set a page-wide flag based on some critieria, you can do that when you subscribe to the results. Here I'm assuming that you will set the showUSer flag if any of the todo items is marked as completed:
this.http.get<JSP[]>('http://jsonplaceholder.typicode.com/todos')
.subscribe(result => {
this.todos = result;
this.showUSers = result.reduce((previous, current) => previous || current.completed, false);
});

Your JSON hasn't any json.completed value, but json[_].completed.

Related

NgFor not being passed an array

Ive tried searching for a solution to this, but I cant find anything less than 3 or 4 years old and those dont map to my problem well. I know what the issue is from the error, but cant seem to track it down, although I general idea that I will note in my description below:
I need to generate a menu from an array of json elements in the following format:
{
"body": [{
"coursename": "introto1",
"course-lesson-name": "Welcome to One! "
}, {
"coursename": "introto2",
"course-lesson-name": "What is One?"
}, {
"coursename": "introto2",
"course-lesson-name": "What Can We do with One?"
}]
}
This response is coming from AWS API gateway and I have set up the following service to handle the call:
menus.service.ts
import { Injectable } from '#angular/core';
import { HttpClient } from '#angular/common/http';
#Injectable({
providedIn: 'root'
})
export class MenusService {
constructor(private http: HttpClient) { }
getLinks(){
return this.http.get('api address');
}
}
Here is the component that uses the services:
navigation.component.ts
import { Component, OnInit } from '#angular/core';
import { MenusService } from './../menus.service';
#Component({
selector: 'app-navigation',
templateUrl: './navigation.component.html',
styleUrls: ['./navigation.component.css']
})
export class NavigationComponent implements OnInit {
links;
constructor(private menusService: MenusService,) { }
ngOnInit(){
this.links = this.menusService.getLinks();
}
}
and here is the component view:
navigation.component.html
<div>
<div class="col-sm-4" *ngFor="let links of links | async">
<a>{{links['course-lesson-name']}}</a>
</div>
</div>
I suspect my issue is in the service and the way Im establishing the get call:
return this.http.get('api address');
What am I missing here?
Here is the error for reference:
ERROR Error: Cannot find a differ supporting object '[object Object]' of type 'object'.
NgFor only supports binding to Iterables such as Arrays.
I bet this.links resolves into an object and not an array.
Do this in your ngOnInit:
ngOnInit(){
this.links = this.menusService.getLinks();
this.links.subscribe(data => console.log(data)); // ensure data here is an array and not an object with `{ body: [....] }`
}
If it is an object like mentioned previously, in your service, try:
getLinks(){
return this.http.get('api address').pipe(
map(res => res.body),
);
}
You can also do that in the component level too but just be sure to get a handle on the array and not on the object for the *ngFor.

How to fetch and use data from a JSON file in Angular7

I am trying to fetch data from a JSON file and display that data in the form
JSON FILE Link:
https://raw.githubusercontent.com/datameet/railways/master/trains.json
I am trying with the below code. But it returns following error in fetchdata.component.ts file:
Property 'json' does not exist on type 'Object'.
fetchdata.component.ts
import { Component, OnInit } from '#angular/core';
import { HttpClient } from '#angular/common/http';
#Component({
selector: 'app-fetchdata',
templateUrl: './fetchdata.component.html',
styleUrls: ['./fetchdata.component.css']
})
export class FetchdataComponent implements OnInit {
private _trainUrl = "https://raw.githubusercontent.com/datameet/railways/master/trains.json
";
items : any;
constructor(private http:HttpClient) {
this.http.get( this._trainUrl)
.subscribe(res => this.items = res.json());
console.log(this.items);
}
ngOnInit() {
}
}
fetchdata.component.html
<select>
<option *ngFor="let item of items" [value]="item.properties.from_station_name">{{item.properties.from_station_name}}</option>
</select>
Please help.
The response probably isn't what you think. I suggest you console.log() the response of your query to see what it actually looks like:
items : any;
constructor(private http:HttpClient) {
this.http.get( this._trainUrl)
.subscribe(res => {
this.items = res.features;
console.log("Response", res);
console.log(res.features)
});
}
You'll see that you actually get something like this in your console:
{type: "FeatureCollection", features: Array(5208)}
features: (5208) [{…}, …]
type: "FeatureCollection"
__proto__: Object
So you can assign your items to the features key as that's what you really need:
constructor(private http:HttpClient) {
this.http.get( this._trainUrl)
.subscribe(res => {
this.items = res["features"];
});
}
Then your select options should show up.
Just letting you know, this isn't the perfect way to do it but it works fine for a small example like this.
I suggest you look into creating a service for any request in the future (doing it in the constructor isn't the best way) and have a look at the RxJS library
There is a difference between Angular's Http and HttpClient module and they are also exported differently.
Http -> is the core module which requires the user to call res.json(). This was common prior to Angular version 4.0.
HttpClient -> is new module since version 4.0. It defaults the communication to json and hence you don't need to call res.json() explicitly.
In short, changing from res.json() to just res will fix the issue
for you.
i.e this.items = res; should be fine.
Also, as a good practice use the ngOnInit lifecycle method instead of the constructor to make any Http calls.
Why do you do this.items = res.json()? Why not just this.items = res? res should already hold the JSON object returned from the GET request. If it is indeed a string try this.items = JSON.parse(res).
Can you try :
private _trainUrl = "https://raw.githubusercontent.com/datameet/railways/master/trains.json";
items : any;
constructor(private http:HttpClient) {}
ngOnInit() {
this.http.get( this._trainUrl).subscribe(res => {
this.items = res;
console.log(this.items);
});
}
You don't need to call .json() function in case you doing plain this.http.get. Angular does that for you. Simply do this.items = res. That will do the trick.
UPD: your JSON object is not an array itself. You as well need to update your template in the following way:
<select>
<option *ngFor="let item of items.features" [value]="item.properties.from_station_name">{{item.properties.from_station_name}}</option>
</select>

How to make Angular 2 render HTML template after a promise in the component is resolved?

For my app, the ItemDetailComponent is where info of an item will be displayed. I have a service that retrieves all items using promise. I use ActivatedRoute to retrieve the item ID from the url path, then run the service, get all items, then find the item with the ID retrieved above, and assign it to selectedItem variable.
Here is item-detail.component.ts:
export class ItemDetailComponent implements OnInit {
private title = 'Item Details'
private selectedItem: object
constructor(
private route: ActivatedRoute,
private itemService: ItemService
) {}
ngOnInit() {
const selectedItemId = this.route.snapshot.params.itemId
return this.itemService.getAllItems()
.then((items) => {
return _.find(items, item => item.itemId === selectedItemId)
})
.then((selectedItem) => {
this.selectedItem = selectedItem
console.log('Inside promise', this.selectedItem)
})
console.log('Outside promise', this.selectedItem)
}
}
And here is item-detail.component.html template so I could display my item, just an example:
<div>
<h1>{{title}}</h1>
<div *ngIf="selectedItem">
<div><label>Item ID: </label>{{selectedItem.itemId}}</div>
</div>
</div>
The app returns nothing but the title unfortunately. I then added the two console.log() commands and found out that the one outside of the promise as well as the html template are rendered before the promise is fulfilled, and no selectedItem is available at that time. How could I force the app to execute them only after the promise is resolved in order to have the selectedItem in place for displayed?
EDIT: I added a new line in the html template to examine further:
<div>
<h1>{{title}}</h1>
<div><label>Item ID 1: </label>{{selectedItem.itemId}}</div>
<div *ngIf="selectedItem">
<div><label>Item ID 2: </label>{{selectedItem.itemId}}</div>
</div>
</div>
The app displays "Item ID 1:" label but with no actual id there. The console shows me an error saying that "Cannot read property 'itemId' of undefined", again confirming that the whole template is rendered before promise resolved and is not re-rendered after data is loaded. So weird.
You could create a Resolver for the route that fetches the desired data.
https://angular.io/api/router/Resolve
https://blog.thoughtram.io/angular/2016/10/10/resolving-route-data-in-angular-2.html
Add a boolean variable in to your class like
private dataAvailable:boolean=false;
and in the subscription to the promise,make this true when the data is available
then((selectedItem) => {
this.selectedItem = selectedItem;
this.dataAvailable=true;
console.log('Inside promise', this.selectedItem)
})
and in the template render when the data is available
<div>
<h1>{{title}}</h1>
<div *ngIf="dataAvailable">
<div><label>Item ID: </label>{{selectedItem.itemId}}</div>
</div>
</div>
It should do the trick
Update
ngOnInit() seems to be just a event handler hook - returning anything won't affect anything it seems. Hence my old answer will not work.
There are other workarounds like using *ngIf or putting it in routes etc. but I wish there was something like resolvePromise(): Promise hook that would put a condition on resolution before rendering.
This is instead of developers putting the boilerplate in every component.
Old answer
Most likely that is because you are missing return statement in the second then.
then((selectedItem) => {
this.selectedItem = selectedItem
console.log():
return selectedItem;//
}
Is it possible that the ChangeDetection is set to OnPush somewhere up the component tree?
If that is the case, the template does not automatically rerender, because nothing triggers the ChangeDetection for this component.
Look out for a Component with the setting changeDetection: ChangeDetectionStrategy.OnPush
#Component({
selector: 'example',
template: `...`,
styles: [`...`],
changeDetection: ChangeDetectionStrategy.OnPush
})
Also you already have a valid solution by using a Resolver you could check if this helps:
export class ItemDetailComponent implements OnInit {
private title = 'Item Details'
private selectedItem: object
constructor(
private route: ActivatedRoute,
private itemService: ItemService,
// the reference to the components changeDetector is needed.
private changeDetectorRef: ChangeDetectorRef
) {}
ngOnInit() {
const selectedItemId = this.route.snapshot.params.itemId
return this.itemService.getAllItems()
.then((items) => {
return _.find(items, item => item.itemId === selectedItemId)
})
.then((selectedItem) => {
this.selectedItem = selectedItem
// this triggers the changedetection and the template should be rerendered;
this.changeDetectorRef.detectChanges();
console.log('Inside promise', this.selectedItem)
});
console.log('Outside promise', this.selectedItem)
}
}
Here is a great article about Angulars ChangeDetection: https://blog.thoughtram.io/angular/2016/02/22/angular-2-change-detection-explained.html

Storing Objects inside Object in Arrays in Angular 2

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

http with Observable in Angular 2 cant use data

i am new to angular 2 and to observables but i wanted to give it a shot. So i have installed the angular-cli and made a simple test project.
All i wanted it to do is read a json file and work with the data inside of a component (the first intention was to make a service but i wanted to start on a low basis).
So i have created a json file in the assets/json folder (testjson.json):
{
"teststring": "test works"
}
then i have imported the http from angular and the rxjs map stuff inside of my content.component.ts file:
import { Component, OnInit } from '#angular/core';
import { Http } from '#angular/http';
import 'rxjs/add/operator/map';
#Component({
selector: 'app-content',
templateUrl: './content.component.html',
styleUrls: ['./content.component.css']
})
export class ContentComponent implements OnInit {
title: string = "Default";
data;
constructor(private http:Http) {
http.get('assets/json/testjson.json').map(res => res.json()).subscribe(data => {this.data = data; this.title = data.teststring; console.log(this.data);});
}
ngOnInit() {
}
}
So far so good, the app prints out the following:
app works!
test works [object Object]
But i want to use this data in the whole component, not only in the constructor. but if i try to console.log "this.data" outside of the constructor (inside the ngOnInit function), it prints undefined in the console.
I know, that it must have something to do with asynch loading but unfortunately i have no clue how to tell the app to wait until this.data is filled.
I hope you can help me with that. Of course in the future i want a service which does that kind of stuff and more than one component should grab data from it.
Thanks in advance!
You should move the initialization code to the initialization method.
Your data becomes available once the callback completes. In your template you can use *ngIf to execute code inside a block once there is data. As long as the *ngIf does not eval to true the inner code will not run.
The only way you can run console.log(data) is from inside the callback or called from the callback because you have to wait until the data is loaded.
content.component.html
<div *ngIf="data">
<span>{{data.teststring}}</span>
</div>
content.component.ts
export class ContentComponent implements OnInit {
title: string = "Default";
data: any = null;
constructor(private http:Http) {
}
ngOnInit() {
this.http.get('assets/json/testjson.json')
.map(res => res.json())
.subscribe(data => {
this.data = data;
this.title = data.teststring;
console.log(this.data);
});
}
}
Edit
In response to the comment below If you abstract out the http call to a service you can see the exact same logic still applies. You are still using the concept of a promise of data and that you can subscribe to that promise once it has completed. The only difference here is the http call is abstracted to a different class.
content.component.ts
export class ContentComponent implements OnInit {
title: string = "Default";
data: any = null;
// inject service
constructor(private contentService:ContentService) {
}
ngOnInit() {
this.contentService.getData()
.subscribe(data => {
this.data = data;
this.title = data.teststring;
console.log(this.data);
});
}
Service
export class ContentService {
constructor(private http:Http) {
}
getData(): IObservable<{teststring:string}> { // where string can be some defined type
return http.get('assets/json/testjson.json')
.map(res => res.json() as {teststring:string});
}