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});
}
Related
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>
I have developed angular2 application using ngrx/effects for making http calls. I have used GIT as reference application. Once the response come from http, i am not able to display it on screen. Its showing [object Object]. Here is my code.
HTML page linked to component.html
<div class="container">
<div class="left-container cf">
<mat-tab-group>
<mat-tab label="Configuration">{{jsons}}</mat-tab>
<mat-tab label="Captured Output">
</mat-tab>
</mat-tab-group>
</div>
</div>
Component.ts
export class ExperimentDetailsComponent implements OnInit {
jsons: Observable<any>;
isLoading: Observable<any>;
constructor(
private store: Store<fromStore.State>
) {
this.isLoading = store.select(fromStore.getIsLoading);
this.jsons = store.select(fromStore.getJson);
console.log(this.jsons)
}
ngOnInit() {
this.store.dispatch(new jsonAction.GetJson());
// this.jsons = this.store.select(fromStore.getJson);
}
}
Effects.ts
export class GetJsonEffects {
#Effect() json$ = this.actions$.ofType(Act.GET_JSON)
.map(toPayload)
.withLatestFrom(this.store$)
.mergeMap(([ payload, store ]) => {
return this.http$
.get(`http://localhost:4000/data/`)
.map(data => {
return new Act.GetJsonSuccess({ data: data })
})
.catch((error) => {
return Observable.of(
new Act.GetJsonFailed({ error: error })
);
})
});
constructor(
private actions$: Actions,
private http$: HttpClient,
private store$: Store<fromStore.State>
) {}
}
As you see, the result of store.select() is an observable. You cannot data bind to it directly.
You can either:
Use the async pipe to make the UI subscribe to the observable for you and extract the data, like:
<mat-tab label="Configuration">{{jsons | async}}</mat-tab>
Or subscribe yourself to the observable.
export class ExperimentDetailsComponent implements OnInit {
jsonSubscription = store.select(fromStore.getJson)
.subscribe(jsons => this.jsons = jsons);
ngOnDestroy() {
this.jsonSubscription.unsubscribe();
}
jsons: any;
// ...
}
That's one thing:
If you are using Http service (from #angular/http module):
The other thing is that you are returning the Response object not the JSON extracted from it. The map() in your effect needs to call data.json(). Like:
return this.http$
.get(`http://localhost:4000/data/`)
.map(data => {
return new Act.GetJsonSuccess({ data: data.json() })
})
Or, as I like, add another map() to make things clear:
return this.http$
.get(`http://localhost:4000/data/`)
// You could also create an interface and do:
// `response.json() as MyInterfaceName`
// to get intellisense, error checking, etc
.map(response => response.json())
.map(data => {
return new Act.GetJsonSuccess({ data: data })
})
If you are using HttpClient service (from #angular/common/http module):
(Available in Angular v4.3+)
In this case you don't need to call .json() yourself, it does it for you, so you don't need that first .map() I suggested.
You can also tell TypeScript about the type you expect the JSON to match by calling the get() like this:
return this.http$
.get<MyInterfaceName>(`http://localhost:4000/data/`)
.map(data => {
return new Act.GetJsonSuccess({ data: data.json() })
})
The get<MyInterfaceName>() bit will make Angular tell TypeScript that the JSON object matches the MyInterfaceName, so you'll get intellisense and error checking based on this (at compile time only, none of this affects runtime in anyway).
HttpClient Documentation
I need to display the data on html that I get from web service. I am able to see the data in a format that I want, but I can't display properly on html. I think -any- in http.get is the problem. I can read data in console without -any- but it works fine with . When it works with it, it still does not print in html properly. Can anyone provide advice on this?
html
<div>{{this.res}}</div>
app.component.ts
import { Component, OnInit } from '#angular/core';
//import { IMovie } from './movie';
import { AppService } from './app.service';
#Component({
selector: 'app-root',
templateUrl: './app.component.html',
styleUrls: ['./app.component.css']
})
export class AppComponent implements OnInit {
res: any[] ;
errorMessage: string;
constructor(private _appService: AppService) { }
ngOnInit(): void { this.getData(); }
getData(): void {
this._appService.getData()
.subscribe(
(res: any []) => this.res = res,
(error: any) => this.errorMessage = <any>error);
}
}
app.service.ts :
Injectable()
export class AppService {
private urlNorth = '';
constructor(private http: HttpClient) { }
getData(): Observable<any> {
const headers = new HttpHeaders();
headers.set('Content-Type', 'text/sml');
headers.set('Accept', 'text/xml');
headers.set('Content-Type', 'text/xml');
return this.http.get<any>(this.urlNorth,{responseType:'text', headers: headers})
.do(data => {
// console.log(data)
var dataParsed = data.replace('<string xmlns="service">', '').replace('</string>', '').replace(/</g, '<').replace(/>/g, '>');
// console.log(dataParsed);
parseString(dataParsed, (err, res) => {
if (err) {
return console.dir('invalid XML');
}
else {
console.log(res);
console.log(res.NewDataSet.Table[0].DataPointName[0]);
}
})
})
.catch(this.handleError);
}
**data in console w/o any **
{{this.res}} in html
I'm pretty sure you don't have to put any at this line in app.service.ts
return this.http.get<any>(this.urlNorth,{responseType:'text', headers: headers})
because get method expects 0 type arguments.
Type any is not the problem. It's just TypeScript annotation to organise your code. The problem is you are refering to the res in inline template as this.res, but you should just res. However it won't work as you think. Looking at your data structure You will have to iterate throught this data due to Table is an array. Additionaly I Highly suggest to always represnt your data as class
export class Apps {
public Table: Array<any>; //here should be another type instead of "any"
/* rest of data if any */
}
Back to your question you should have in your html file <div>{{res}}</div> but that's just print your object as string if I good remember. So to properly access your data you should iterate through table using *ngFor
<div *ngFor="let el of res.NewDataSet.Table">
<span>{{el.BackColor}}</span>
<!-- all other data -->
</div>
It looks as though the data is coming back. I'll answer your initial question first (since you added a few issues in comments):
My guess is when you get data back, it's not showing because it's HTML, and angular doesn't like injecting html.
Add this to your TS:
import { DomSanitizer, SafeHtml } from '#angular/platform-browser';
res[]: safeHTML;
And change your html to this:
<div [innerHTML]="res"></div>
As mentioned in a previous answer, this is a solution for a single return of res, not an array of different htmls. If it's an array, you'll have to handle it accordingly. for instance:
<ng-container *ngFor="let r of res">
<div [innerHTML]="r">
</ng-container>
Context:
I have an Angular 2+ application that makes calls to a web API containing URLs for a src attribute on a script tag that is created by a loadScript function in the AfterViewInit lifecycle hook.
The web API returns a JsonResult and is yielding the data I expect. I was able to interpolate some of the data in the component's template.
Additionally, before I added the call to the web API, the loadScript function was working with a hard-coded argument.
Reading a thread on github. A "member" stated that scripts are not supposed to be loaded on demand. So what I implemented with the loadScript function is essentially a work around, but how else would load them? I don't want to have a seemingly endless amount of script tags sitting in the index.html file.
import { Component, OnInit, AfterViewInit } from '#angular/core';
import { ActivatedRoute } from '#angular/router';
import { Http } from '#angular/http';
#Component({
selector: 'app-agriculture-roadmap',
templateUrl: './agriculture-roadmap.component.html',
styleUrls: ['./agriculture-roadmap.component.css']
})
export class RoadmapComponent implements OnInit, AfterViewInit {
constructor(private _httpService: Http, private _route: ActivatedRoute)
{
}
apiRoadmaps: { roadmapName: string, pdfRoadmapURL: string, jsRoadmapURL: string };
ngOnInit() {
this._httpService
.get('/api/roadmaps/' + this._route.params)
.subscribe(values => {
this.apiRoadmaps = values.json() as { roadmapName: string, pdfRoadmapURL: string, jsRoadmapURL: string };
});
}
async ngAfterViewInit() {
await this.loadScript(this.apiRoadmaps.jsRoadmapURL);
}
private loadScript(scriptUrl: string) {
return new Promise((resolve, reject) => {
const scriptElement = document.createElement('script')
scriptElement.src = scriptUrl
scriptElement.onload = resolve
document.body.appendChild(scriptElement)
})
}
}
If you are using angular cli .
Then place these scripts in
angular-cli.json file under scripts array
scripts:[
.....
]
Please refer this [link] (https://rahulrsingh09.github.io/AngularConcepts/faq)
It has a question on how to refer third party js or scripts in Angular with or without typings.
i'm stucked on a problem and i don't know how to get out of it.
I have two component sibiling:
One that show a list of products with a button for each product that delete from the cart the single product with a POST call at one REST API.
And another component that simple call a REST API for get the totals of the cart and show it.
The problem is that when i delete correctly the item from the cart, obviously the cart total doesn't update itself.
So, i've searched on the communities and i think that there are two different solutions:
Use a shared service;
Use #Input and #Output
I've tried using the first option, but without success, i tried also with #input and #Output but i don't think that i really understand how to use it between two components that aren't Parent > Child or opposite.
What i need is to call the function GetTotals inside the CARTTOTAL.COMPONENT from the CARTITEMS.COMPONENT for updating the prices.
I've tried to inject the same service in both components and call the function from the first one, but seems doesn't work.
Here the code:
cartitems.component.ts
import { Component, OnInit, Inject, Output } from '#angular/core';
import { ManageGuestCartService } from '../manageCartServices/addtoguestcart.service';
//import { CarttotalComponent } from '../carttotal/carttotal.component';
// Service for guest total cart
import { TotalguestcartService } from '../manageCartServices/totalguestcart.service';
#Component({
selector: 'app-cartitems',
templateUrl: './cartitems.component.html',
styleUrls: ['./cartitems.component.css'],
providers: [ManageGuestCartService, TotalguestcartService]
})
export class CartitemsComponent implements OnInit {
itemofcart:any[];
constructor(private _guestcartservice: ManageGuestCartService, private _totalguestcart: TotalguestcartService) { }
ngOnInit() {
this.listCartItems();
}
listCartItems() {
return this._guestcartservice.getCartDetails()
.subscribe(data => {
this.itemofcart = data;
//console.log(this.itemofcart);
},
(err) => {
//alert('vuoto');
});
}
removeProductFromCart(itemid) {
return this._guestcartservice.deleteProductFromCart(itemid)
.subscribe(data => {
// this.itemofcart = data;
// console.log(this.itemofcart);
this.listCartItems();
this._totalguestcart.getTotals();
},
(err) => {
alert('errore');
});
}
}
carttotals.component.ts
import { Component, OnInit, Input} from '#angular/core';
// Service for guest total cart
import { TotalguestcartService } from '../manageCartServices/totalguestcart.service';
#Component({
selector: 'app-carttotal',
templateUrl: './carttotal.component.html',
styleUrls: ['./carttotal.component.css'],
providers: [TotalguestcartService]
})
export class CarttotalComponent implements OnInit {
constructor(private _totalguestcart: TotalguestcartService) { }
totals:any[];
ngOnInit() {
this.retrieveTotals();
}
retrieveTotals() {
return this._totalguestcart.getTotals()
.subscribe(data => {
this.totals = data;
console.log(this.totals);
},
(err) => {
alert(err);
});
}
}
totalguestcart.service.ts
import { Injectable } from '#angular/core';
import { Http, Headers, RequestOptions} from '#angular/http';
#Injectable()
export class TotalguestcartService {
constructor(public http: Http) { }
public getTotals() {
let cartid;
cartid = localStorage.getItem("guestCartId");
let contentHeaders = new Headers();
contentHeaders.append('Accept', 'application/json');
contentHeaders.append('Content-Type', 'application/json');
return this.http.get(URL, { headers: contentHeaders})
//.map(res => res.json())
.map((res) => {
if(res.status == 404) {
return res.status;
} else {
return res.json();
}
});
}
}
Can someone give me the correct way to find a solution to this issue? all the feeds are accepted :)
Thanks in advance!
As mentioned, you need to mark your providers in the module, so that you share the same service between your components.
Shared Service would be best here. From my understanding you want to fire an event in the carttotals component, when deleting an item in the cartitems component.
Well we can set up Observable which will fire that event. So in your totalguestcart service add this:
private fireEvent = new Subject<boolean>();
event = this.fireEvent.asObservable();
emitEvent(bool: boolean) {
this.fireEvent.next(bool);
}
Here we are just using boolean values, as you do not need to pass any specific values, but only fire an event.
Then when you are performing the deletion, let's notify the other component, which subscribes to this, that the method should be fired.
removeProductFromCart(itemid) {
return this._guestcartservice.deleteProductFromCart(itemid)
.subscribe(data => {
this.itemofcart = data;
this.listCartItems();
this._totalguestcart.emitEvent(true); // add this!!
},
(err) => {
alert('error');
});
}
And in your cart totals, subscribe to this in your constructor, and execute the getTotals method:
constructor(private _totalguestcart: TotalguestcartService) {
_totalguestcart.event.subscribe(res => {
this.retrieveTotals();
})
}
this.retrieveTotals will then be fired each time you are deleting an item. Of course this can be used in other methods as well, like adding and updating (if you need it).
Hope this helps! :)
Throw out the service TotalguestcartService out of the providers of your components and put it into the providers of the app-module: What is happening: each component is getting a local copy of the service, so they cannot exchange information, as there are TWO services injected. Putting it global (app.module) provides it for every component as long as the component doesn't do an own provider.