I've worked out how to import a json object from http get and list the results out via *ngFor.
What I can't work out is how to run an interface against it. My interface file is here:
import {Offer} from './offer';
and I have my http get request here:
constructor(private _httpService: HTTPTestService) { }
offers: "";
onTestGet() {
this._httpService.getOffers()
.subscribe(
data => { this.offers = data.offers; },
error => alert(error),
() => console.log("Finished")
);
}
But how do I run the "Offer" interface past the object I get back?
You need to be careful with interfaces in TypeScript. Interfaces are only for design and type checking but don't exist at runtime.
You can define an array of Offer for offers and cast the data you receive to Offer[]. But each element isn't of type Offer. This allows to check the structure of objects during compilation...
Here is a sample:
constructor(private _httpService: HTTPTestService) { }
offers: Offer[]; // <----
onTestGet() {
this._httpService.getOffers()
.subscribe(
data => { this.offers = <Offer[]>data.offers; }, // <----
error => alert(error),
() => console.log("Finished")
);
}
Related
in my project I'm fetching data from an API (trip entities). This is my model:
//Trip.model.ts
export class Trip{
created_at?:Date;
updated_at?:Date;
.... bunch of fields
}
In my component I'm fetching the data and assigning it to the trips variable. However, when I'm trying to access any of the items in the trips array I get 'undefined'. I also can't loop through it, I tried both forEach and for...in/of.
I tried using an interface instead of a class but with no luck. How can I loop through that array of objects in order to use the data in it?
Component's code:
userName:string='';
trips:Trip[]=[];
moment:any=moment;
usersData:any={};
constructor(private auth: AuthService,
private storage: LocalStorageService,
private translate: TranslateService,
private tripService: TripService){}
ngOnInit(): void {
console.log(this.translate.currentLang)
this.userName=localStorage.getItem('username')!;
this.fetchTrips();
this.fetchPics();
}
fetchTrips() {
this.tripService.getTrips().subscribe({
next: data => {
data[0].data.forEach((value) => {
let trip: Trip = plainToInstance(Trip,value);
this.trips.push(trip);
});
}, error: error => {
console.log(error);
}
});
}
//fetchPics because I want to extract
//user's profile pics' urls from the trips array
fetchPics(){
console.log(this.trips);
console.log(this.trips[0]);
this.trips.forEach((trip)=>{
console.log(trip);
});
}
getTrips service method:
getTrips(){
return this.http.get<any>(Api.API+Endpoints.TRIP);
}
This is what shows when I
console.log(this.trips)
after assignment.
Data from the API:
Pictures cropped to make them more readable.
You are trying to get access to this.trips value before you actually have your data on it.This happens becouse you get that data asynchronously, just inside the subscribe of this.tripService.getTrips()
So, in order to solve your problem, you just need to move this invoke:
this.fetchPics();
inside the subscribe of fetchTrips() method, like this:
fetchTrips() {
this.tripService.getTrips().subscribe({
next: data => {
data[0].data.forEach((value) => {
let trip: Trip = plainToInstance(Trip,value);
this.trips.push(trip);
this.fetchPics();
});
}, error: error => {
console.log(error);
}
});
}
The function fetchPics() executes before your getTrips call ends. You need to call the method only after your HTTP call ends, and after you populate your trips array successfully.
fetchTrips() {
this.tripService.getTrips().subscribe({
next: data => {
//Populate your trips array
data[0].data.forEach((value) => {
let trip: Trip = plainToInstance(Trip,value);
this.trips.push(trip);
});
// this is where you need to call
this.fetchPics();
}, error: error => {
console.log(error);
}
});
}
This is happening because of JS is asynchronous. you are making an http request here, that may take some time to get data from server. let's assume that might take 1 minute untill then compiler will not stop it's execution process In that 1 min of time it will execute next statements.because of that your fetchpics() method is being executed before fecthtrips() execution done. To overcome this we can use async, await as like below.
async fetchTrips() {
this.tripService.getTrips().subscribe({
next: data => await {
data[0].data.forEach((value) => {
let trip: Trip = plainToInstance(Trip,value);
this.trips.push(trip);
});
}, error: error => {
console.log(error);
}
});
}
I am new to all JavaScript and angular. so I am struggling to do the following:
I have the following service, to read X from a local JSON file. The X is what user select from a dropdownbox:
getBySector(sector){
this.http.get('../../assets/Sectors.json').map(res => res).subscribe
(res => {
this.SectorsArray = res as ISectors[];
this.SectorsArray= res.find(item=>item.Sector===sector);
console.log(this.industrySectorsArray);
return this.industrySectorsArray;
},
(err: HttpErrorResponse) => {
console.log (err.message);
}
)
}
as an additional note, I have an interface which is ISector and matches the JSOn file.
The above code give me in Console the exact thing I expect. which is the following:
{IndustrySector: "Households", isSelected: "false", dataSubjectCategories: Array(2), dataTypeCategories: "Data", SubIndustries: Array(2)}
HOW can I return the above object/json output to ms TS file where I have called the service?
I have done the followings which are failed:
//even this failed:
console.log(this.readjsonService.getBySector(mission));
//
var output:Isector;
output=this.readjsonService.getBySector(mission)
// cannot subscribe to it as well
BTW, the find gives me the following error:
error TS2339: Property 'find' does not exist on type 'Object'.
UPDATE:
I solved the issue the code had with the help of people who replied. But the code got another error, although it works fine. t says:
"Cannot read property 'dataSubjectCategories' of undefined"
dataSubjectCategories is one of the key in the ISector: here is the ISector:
export interface ISectors {
IndustrySector: string;
isSelected: string;
dataSubjectCategories:string[];
dataTypeCategories:string[];
SubIndustries:[{
IndustrySector: string;
isSelected: string;
dataSubjectCategories:string[];
dataTypeCategories:string[];
SubIndustries:[{}]
}]
}
Please help to resolve this. Thanks a lot.
Normally, your service should just be returning the Observable and should not include the subscribe. Best practice suggests that you subscribe as close to the UI as possible.
My service methods look like this:
getProducts(): Observable<IProduct[]> {
return this.http.get<IProduct[]>(this.productUrl).pipe(
tap(data => console.log('All: ' + JSON.stringify(data))),
catchError(this.handleError)
);
}
getProduct(id: number): Observable<IProduct | undefined> {
return this.getProducts().pipe(
map((products: IProduct[]) => products.find(p => p.productId === id))
);
}
Using the generic parameter on the get: get<IProduct[]> helps Angular automatically map the returned response to an array of data, ISectors in your example.
The calling code in the component then looks like this:
getProduct(id: number) {
this.productService.getProduct(id).subscribe(
product => this.product = product,
error => this.errorMessage = <any>error);
}
Notice that here is where we subscribe. It then gets the product in the first function passed to the subscribe method.
You can find the complete example here: https://github.com/DeborahK/Angular-GettingStarted/tree/master/APM-Final
I know this is probably an easy solution. I am building an Ionic 3 app and was able to connect to a json data file and print the objects to the console, but I am unable to pass the item to my search function. The error I get on the page is Runtime Error - Cannot read property "filter" of undefined - I am guessing its because the item isn't being made available.
data.ts
import { Injectable } from "#angular/core";
import { Http } from "#angular/http";
import "rxjs/add/operator/map";
#Injectable()
export class Data {
items: any;
constructor(public http: Http) {
this.http
.get("./assets/data/plaques.json")
.map(res => res.json())
.subscribe(data => {
this.items = data;
console.log(data);
});
}
filterItems(searchTerm) {
return this.items.filter(item => {
return (
item.veteran_first.toLowerCase().indexOf(searchTerm.toLowerCase()) > -1
);
});
}
}
You're trying to access this.items before it's been initialized.
http.get is an asynchronous function, and this.items may not be set when you access filterItems.
I highly recommend reading this question/answer which will go into greater understanding of the asynchronous nature of javascript.
could you please wrap it with zone() like this :
this.zone.run(() => {
this.items = data;
});
I am attempting to build an Angular 4 based service (backed by a C#-based RESTful API) which will allow for storing and retrieval of web-application wide settings. Something like a key-value pair based lookup for all common application settings.
The idea is this:
Retrieve all settings on start of the application from the C# WebApi based RESTful service into a client-side JavaScript array and stored in the Angular 4 service.
If any specific setting is needed, first look in the locally retrieved array for said setting and return that.
If said setting is not found, make a call to the previously mentioned WebAPI service for that specific setting to see if it is available and retrieve that. Also, push said retrieved setting in the client-side array so I don't have to make the call again until needed.
The problem I am having is this:
I want to return an Observable, even if I have the setting in the array locally, so that I can handle the situation of the web application having to wait for the setting to be retrieved.
I also want to handle situations where the API call for the specific setting fails.
See below for what I have now, any help appreciated.
'use strict';
import { Injectable } from '#angular/core';
import { Http, Response, Headers, RequestOptions } from '#angular/http';
import { Observable } from 'rxjs/Observable';
import { EmptyObservable } from 'rxjs/observable/EmptyObservable';
import 'rxjs/add/operator/map';
import 'rxjs/add/operator/share';
import { Subject } from 'rxjs/Subject';
import { BehaviorSubject } from 'rxjs/BehaviorSubject';
import { ReplaySubject } from 'rxjs/ReplaySubject';
import { SettingAPIURLs } from '../global.urls';
import * as Interfaces from "../interfaces/models.interface";
import * as Classes from "../classes/models.classes";
#Injectable()
export class SettingsService {
private _settings: BehaviorSubject<Classes.Setting[]>;
private settingDataStore: {
settings: Classes.Setting[]
}
constructor(private http: Http) {
this.settingDataStore = { settings: [] }
}
loadSettings() {
this.http.get(SettingAPIURLs.GetSettings)
.map(response => response.json())
.subscribe(data => {
this.settingDataStore.settings = data;
this._settings.next(Object.assign({}, this.settingDataStore).settings);
}, error => {
console.log("There were errors in attempting to retrieve the web application's settings: " + error);
});
}
get CurrentSettings() {
return this._settings.asObservable().share();
}
retrieveSetting(SettingName: string): Observable<Classes.Setting> {
/*
I am lost as to what to do here.
*/
let returnedObservable: Observable<Classes.Setting> = new Observable<Classes.Setting>();
if (typeof (SettingName) === "undefined" || SettingName === null) {
return new EmptyObservable();
}
this.http.get(SettingAPIURLs.GetSetting + "?SettingName=" + SettingName)
.map(response => response.json())
.first();
}
}
Angular has a built in solution for your problem called resolve. This article explains how to use it:
https://blog.thoughtram.io/angular/2016/10/10/resolving-route-data-in-angular-2.html
The idea is that you have the data for the page loaded before even running the logic for the page.
The basic implementation is that you need to write a resolver that will go ahead and make your settings api call. Then you just need to hookup your routing to start using the resolver.
If I understood correctly , you want to send the setting if it exists else do http call . I am assuming your settings object is a key-value pair object. I am not sure why you have it is an array.
You can do something like this
// extract fetch part separately . If settings exist return it or do http call
fetchSettings(){
return this._settings.asObservable
.flatMap(settings => settings ?
Observable.of(settings) :
this.http.get(SettingAPIURLs.GetSettings)
.map(response => response.json())
.do(data => {
this.settingDataStore.settings = data;
this._settings.next(
Object.assign(
{},
this.settingDataStore).settings);
})
.catch(error => {
console.log("..... " + error);
});
}
loadSettings(){
this.fetchSettings.subscribe(data => {
console.log('load Settings success', data);
});
}
retrieveSetting(SettingName: string): Observable<Classes.Setting> {
this.fetchSettings()
.flatMap(settings => settings[SettingName]?
Observable.of(settings[SettingName]) :
this.http.get(SettingAPIURLs.GetSetting +
"?SettingName=" + SettingName)
.map(response => response.json())
.do(res=> this._settings.value[SettingName] = res)
);
}
If any of the above HTTP calls fail you will get an exception which you can handle in the component like this :
this.settingsService.retrieveSetting().subscribe(successFunction, failureFunction);
You can either show error message to user or consider it as blank based on your site requirements.
For learning, I am trying to create a simple Todo application using Angular 2 + Django Rest Framework.
In order to immediately display the items saved in the input form, I want to implement the function of acquiring the latest one and displaying it.
JSON format WEB API for acquiring the latest one has the following structure.
function for getting this API is described in todo.service.ts
#Injectable()
export class TodoService {
todo: Todo[] = [];
newtodo: NewTodo[] = [];
private Url = `http://127.0.0.1:8000/api/todo/`
private headers = new Headers({'Content-Type': 'application/json'});
constructor(
private http: Http
){}
// Acquire one latest todo added
getNewTodo(): Promise<NewTodo[]> {
return this.http
.get(this.Url+"?limit=1")
.toPromise()
.then(res => res.json())
.catch(this.handleError)
}
Components are described in todo.component.ts
#Component({
selector: 'todo-list',
templateUrl: '../templates/todo-list.component.html',
styleUrls: ['../static/todo-list.component.css']
})
export class TodoListComponent {
todos: Todo[] = [];
newtodo: NewTodo[] = [];
newtodos: Todo[] = [];
#Input() todo: Todo = new Todo();
save(): void {
this.todoService
.create(this.todo);
}
}
The point I want to teach is the following points.
1.Execute service's getNewTodo () when save () of the component is executed and store the obtained return value in the array newtodo.
2.Since only the results part is necessary for the acquired JSON, pull out the results part, push it to the array newtodos and pass it to html.
For 1, I think that you should write the following description in save ()
this.todoService
.getNewTodo()
.then(newtodo => this.newtodo = newtodo);
I can not understand what to write about 2.
I'd like to tell you how to solve it.
You can step into the JSON and obtain the content of results with the following:
getNewTodo(): Promise<NewTodo[]> {
return this.http
.get(this.Url+"?limit=1")
.toPromise()
.then(res => res.json().results) // here
.catch(this.handleError)
}
You are not showing use the create-method in the service, but whatever it looks like, return a promise, so that we in the component can inside the callback call getNewTodo:
save() {
this.todoService
.create(this.todo)
.then(data => {
this.getNewTodo(); // here
})
}
and the getNewTodo that calls the method in the service:
getNewTodo() {
this.todoService
.getNewTodo()
.then(newtodo => {
this.newtodo = newtodo
});
}