I am new to Angular2 and just started to work with Http request and observables.
Previously I worked with .NET and MySql, and I am now trying to learn the best practice working with data from API’s.
I am used to join tables, and I would like to find the best way to combine json data.
In this example I want the user to fill a form and type his phone number.
The prefix of the phone number is a dropdownlist with country code and prefix f.ex. Germany +49
Therefor I need an object: { “Germany”:”49”, Cambodia:"855" ….}
I make 2 http request to country.io:
http://country.io/phone.json // e.g. DE: “Germany”
http://country.io/names.json // e.g. DE: “49”
From these 2 request I used the code below to make my new json object : myPhonePrefixObject
I think the code is too long, and that it must be possible to do it in a better way.
country-service.ts:
import { Injectable } from '#angular/core';
import { Http } from '#angular/http';
import 'rxjs/Rx';
import { Observable } from 'rxjs/Observable';
#Injectable()
export class CountryService {
constructor( private _http:Http) { }
getCountryCode(): Observable<any> {
return this._http.get('http://crossorigin.me/http://country.io/phone.json')
.map(countryCodes => countryCodes.json());
}
getPhonePrefix(): Observable<any> {
return this._http.get('http://crossorigin.me/http://country.io/names.json')
.map(phonePrefix => phonePrefix.json());
}
}
Code inside userform.component in where I import the CountryService
myPhonePrefixObject;
this.countryPhonePrefix()
.then((pp) => {
myPhonePrefixObject = pp;
})
.catch((err) => {
console.log(err);
});
private getCountryCode() {
return new Promise((resolve) => {
this._countryService.getCountryCode()
.subscribe(
res => resolve(res)
);
});
}
private getPhonePrefix() {
return new Promise((resolve, reject) => {
return this._countryService.getPhonePrefix()
.subscribe(
res => resolve(res),
error => reject(error)
);
});
}
private countryPhonePrefix() {
return new Promise((resolve, reject) => {
let cc: Object;
this.getCountryCode()
.then((cCode) => {
cc = cCode;
return this.getPhonePrefix()
})
.then((pPrefix) => {
let pp: Object = {};
Object.keys(cc).forEach((key, index) => {
pp[cc[key]] = pPrefix[key];
});
resolve(pp);
})
.catch((err) => {
reject(err);
});
});
}
.NET developer here too!
To deal with multiple streams, you will need aggregation methods. In this case, you want to yield the object based on the result of 2 streams (HTTP requests), the aggregation method you are looking for is combineLatest. It combines the 2 streams and let you define the output data based on 2 sources:
getCombinedData(): Observable<Data> {
return this.getCountryPhones().combineLatest(this.getCountryNames(),
(phoneData, nameData) => {
var resultData = {};
Object.keys(nameData).forEach((key) => {
resultData[nameData[key]] = phoneData[key];
});
return resultData;
});
}
Plunker: http://plnkr.co/edit/agUPNujG3NnbKI6J3ZVJ?p=preview
Related
I have to fetch 2 api from backend, and try to get the result from this two. but, at the moment, the JSON result I get from the first API is object Array in JSON. I need to pass the id from first API(using setState) to second API for path variables. But when I do in my way, it fail to retrieve the data. Consider the code below:
componentDidMount(){
// console.log(loginEmail)
fetch(`http://localhost:9000/api/item/list`,)
.then((resp)=>{
resp.json().then((res)=>{
console.log(res.data);
// localStorage.setItem('id', res.data.user_info.id);
this.setState({data: res.data});
}
)
})
const id = this.state.data.id;
fetch(`http://localhost:9000/api/item/photo/view/${id}`,)
.then((resp)=>{
resp.json().then((res)=>{
console.log(res);
// localStorage.setItem('id', res.data.user_info.id);
this.setState({res});}
)
})
}
The problem is that fetch returns a Promise so, at the line
const id = this.state.data.id;
You do not have data populated yet.
You have to concatenate the two requests in a way like the following:
componentDidMount() {
fetch(`http://localhost:9000/api/item/list`)
.then((resp) => {
// return the id
})
.then((id) => {
fetch(`http://localhost:9000/api/item/photo/view/${id}`)
.then((resp) => {
// do what you need with the result
})
})
}
Fetch is asynchronous, which means javascript will
fetch data on the first call with no waiting, and continue
to the second fetch call where the id is not defined or Null.
In order to fix that you can use promises as follow
My code example
import React from "react";
class Home extends React.Component {
constructor() {
super();
this.state = {
res: [],
}
}
// http://jsonplaceholder.typicode.com/users
fetchData(url) {
return new Promise((resolve, reject) => {
fetch(url)
.then((resp) => {
resp.json().then((res) => {
console.log(res);
// localStorage.setItem('id', res.data.user_info.id);
resolve(res);
}
)
})
})
}
async componentDidMount() {
let data = await this.fetchData("http://jsonplaceholder.typicode.com/users");
console.log("data :", data);
let id = data[0].id;
console.log("Id :", id);
let newData = await this.fetchData(`http://jsonplaceholder.typicode.com/users/${id}`);
this.setState({ res: newData });
}
render() {
return (
<div>
Call API
</div>
)
}
}
export default Home
Adapted on your code
fetchData(url) {
return new Promise((resolve, reject) => {
fetch(url)
.then((resp) => {
resp.json().then((res) => {
console.log(res.data);
// localStorage.setItem('id', res.data.user_info.id);
resolve(res.data);
}
)
})
})
}
async componentDidMount() {
// console.log(loginEmail)
let data = await this.fetchData("http://localhost:9000/api/item/list");
let id = data.id;
let newData = await this.fetchData(`http://localhost:9000/api/item/photo/view/${id}`);
this.setState({ res: newData });
}
You need to make sure that each id gets its relevant results.
async componentDidMount() {
await fetch(`http://localhost:9000/api/item/list`)
.then(async (resp) => {
let req_ = resp.map((item)=>{
return await fetch(`http://localhost:9000/api/item/photo/view/${item.id}`)
})
let result = Promise.all(req_)
console.log(result)
})
}
I am trying to make an API call in React to return JSON data but I am a bit confused on how to go about this. My API code, in a file API.js, looks like this:
import mockRequests from './requests.json'
export const getRequestsSync = () => mockRequests
export const getRequests = () =>
new Promise((resolve, reject) => {
setTimeout(() => resolve(mockRequests), 500)
})
It is retrieving JSON data formatted like this:
{
"id": 1,
"title": "Request from Nancy",
"updated_at": "2015-08-15 12:27:01 -0600",
"created_at": "2015-08-12 08:27:01 -0600",
"status": "Denied"
}
Currently my code to make the API call looks like this:
import React from 'react'
const API = './Api.js'
const Requests = () => ''
export default Requests
I've looked at several examples and am still a bit confused by how to go about this. If anyone could point me in the right direction, it would be greatly appreciated.
EDIT: In most examples I've seen, fetch looks like the best way to go about it, though I'm struggling with the syntax
Here is a simple example using a live API (https://randomuser.me/)... It returns an array of objects like in your example:
import React from 'react';
class App extends React.Component {
state = { people: [], isLoading: true, error: null };
async componentDidMount() {
try {
const response = await fetch('https://randomuser.me/api/');
const data = await response.json();
this.setState({ people: data.results, isLoading: false });
} catch (error) {
this.setState({ error: error.message, isLoading: false });
}
}
renderPerson = () => {
const { people, isLoading, error } = this.state;
if (error) {
return <div>{error}</div>;
}
if (isLoading) {
return <div>Loading...</div>;
}
return people.map(person => (
<div key={person.id.value}>
<img src={person.picture.medium} alt="avatar" />
<p>First Name: {person.name.first}</p>
<p> Last Name: {person.name.last}</p>
</div>
));
};
render() {
return <div>{this.renderPerson()}</div>;
}
}
export default App;
Does it make sense? Should be pretty straight forward...
Live Demo Here: https://jsfiddle.net/o2gwap6b/
You will want to do something like this:
var url = 'https://myAPI.example.com/myData';
fetch(url).then((response) => response.json())
.then(function(data) { /* do stuff with your JSON data */})
.catch((error) => console.log(error));
Mozilla has extremely good documentation on using fetch here that I highly recommend you read.
The data parameter in the second .then will be an object parsed from the JSON response you got and you can access properties on it by just using the property label as was in the JSON. For example data.title would be "Request from Nancy".
If you are struggling with fetch, Axios has a much simpler API to work with.
Try this in your API.js file (of course install axios first with npm i --save axios):
import axios from 'axios'
import mockRequests from './requests.json'
export const getRequests = (url) => {
if (url) {
return axios.get(url).then(res => res.data)
}
return new Promise((resolve, reject) => { // you need to return the promise
setTimeout(() => resolve(mockRequests), 500)
})
})
In your component, you can access the getRequests function like so
import React, { Component } from 'react'
import { getRequests } from './API.js'
class App extends Component {
state = {
data: null
}
componentWillMount() {
getRequests('http://somedomain.com/coolstuff.json').then(data => {
console.log(data)
this.setState({ data })
})
}
render() {
if (!this.state.data) return null
return (
<div className='App'>
{this.state.data.title}
</div>
)
}
}
export default App
I've a promise in Parent class, whenever I call the promise from child class, it is returning the undefined, instead of executing the promise and returning the resul.
import {newsApiKey as APIKEY, newUrl as APIURL} from "./secretToken";
class News{
constructor(){
this.token = APIKEY;
this.url = APIURL;
this.source = 'bbc-news&';
}
topNews(){
const bbcNews = fetch(`${this.url}?source=${this.source}&sortBy=top&apiKey=${this.token}`);
bbcNews.then(response => {
if (!response.ok) {
throw Error(response.statusText);
}
return response.json()
})
.then(json => {
console.log(json.articles);
return json.articles;
})
.catch((err) => {
return err.message;
});
}
}
export { News as default};
CHILD CLASS
import News from "./news";
class StickyNote extends News{
displayNews(){
let bbcNews = super.topNews(); // It is returning only undefined
if (typeof bbcNews != 'undefined') {
console.log(bbcNews); //
}
}
}
topNews never returns anything, so the result of calling it is undefined.
You probably wanted a return here:
topNews() {
const bbcNews = fetch(`${this.url}?source=${this.source}&sortBy=top&apiKey=${this.token}`);
return bbcNews.then(response => {
// ^^^^^^
if (!response.ok) {
throw Error(response.statusText);
}
return response.json()
})
.then(json => {
console.log(json.articles);
return json.articles;
})
.catch((err) => {
return err.message;
});
}
Also note that displayNews will need to use the promise it receives:
displayNews(){
super.topNews().then(articles => {
// ...use articles
});
}
(Normally you'd also have a catch there at the endpoint of consumption, but as you've converted rejections into resolutions...)
Note: That code has a bit of an anti-pattern in it: It converts a rejection into a resolution with an error message. Anything using the promise will never see a rejection, only resolutions with varying return types (whatever json.articles is or a string). In general, it's better to allow rejections to propagate, and handle them at the ultimate point of consumption of the entire chain (displayNews, I believe, in your example). You might transform their content, but not convert them from a rejection into a resolution.
FWIW, I'd probably rewrite that like so:
topNews() {
return fetch(`${this.url}?source=${this.source}&sortBy=top&apiKey=${this.token}`)
.catch(_ => {
throw new Error("network error");
})
.then(response => {
if (!response.ok) {
throw new Error(response.statusText);
}
return response.json();
})
.then(data => { // "data", not "json" -- it's not JSON anymore
return data.articles;
});
}
...which ensures that the caller either gets a resolution with the articles, or a rejection with an Error, so:
displayNews(){
super.topNews()
.then(articles => {
// ...use articles
})
.catch(err => {
// ...show error
});
}
I have one application which include login and home component,
login.service.ts
let body = JSON.stringify(data);
console.log("logged in user",body);
return this._http.post('http://localhost:8080/api/user/authenticate', body, { headers: contentHeaders })
.map(res => res.json())
.map((res) => {
var token1:any = res;
console.log(token1);
if (token1.success) {
localStorage.setItem('auth_token', token1.token);
this.LoggedIn = true;
}
return res.success;
});
}
isLoggedIn() {
return this.LoggedIn;
}
in this service i am getting token in variable token1 and isLogged method contain
constructor(private _http: Http) {
this.LoggedIn = !!localStorage.getItem('auth_token'); }
Login.component.ts
login(event, username, password)
{
this.loginService.login(username, password)
.subscribe(
response => {
this.router.navigate(['/home']);
alert("login successfull");
},
error => {
alert(error.text());
console.log(error.text());
}
);
From this login i can able to authenticate and and its routing to home component,
Home.serice.ts
getClientList()
{
let headers = new Headers();
headers.append('Content-Type', 'application/json');
let authToken = localStorage.getItem('auth_token');
headers.append('X-auth-Token', 'authToken')
return this._http.get('http://localhost:8080/api/v1/client/list?isClient=Y', {headers})
.map(res => res.json())
}
Home.component.ts
onTestGet()
{
this._httpService.getClientList()
.subscribe(
data => this.getData = JSON.stringify(data),
error => alert(error),
() => console.log("finished")
);
}
now question is how can i access that token in home component which is in token1 varible(login) i have tired to getitem token.but i am getting token null.please anybody help me.
thanks in advance
localStorage.getItem('auth_token')
This should work, but you are getting null, because lifecycle of the data different.
I suggest you to use Subject construction for this purpose, especially you already have service with data.
Example:
loginInfo$ = new Subject();
private _logininfo = null;
getLoginData () {
if(!_logininfo) {
this.http..... { this._loginInfo = data;
this.loginInfo$.next(data); }
return this.loginInfo$.first()
}
else return Observable.of(this._logininfo);
}
So now, your service at the same time storage of data and handler for missing login.
I have problem with my Observable in my service.
I need to fetch data for 3 players. My subscription sign data from service to local variable and push it into array. Fine, but when i return data from if statement i have bug. I can see only one of 3 players. How can i store all data for whole life time of my app?
Regards.
Service:
getData(query): Observable<any> {
if(this.dataFromDb)
{
return Observable.of(this.dataFromDb);
}
return this.http.get(query)
.map(res => res.json())
.do(res => this.dataFromDb = res)
.catch(err => Observable.throw(err.json() || 'Błąd');
}
}
Component:
export class FriendsComponent implements OnInit {
myDataFromDb: any[] = [];
constructor(public dataService: DataService) {
}
private getDataFromDb(query) {
this.dataService.getData(query).subscribe((data) =>
{
this.myDataFromDb.push(data);
console.log(data);
});
}
ngOnInit() {
for (let i of this.dataService.friends) {
this.dataService.query = `${this.dataService.apiUrl}${i.nick}${this.dataService.apikey}`;
this.getDataFromDb(this.dataService.query);
}
console.log(this.myDataFromDb);
}
}
And some photo of problem:
Data on start
Data after route change.
You must use a object to "cache" the response. Personally I'll choose send to my function the nick and the apiKey, but as you send query, you can do
dataFromDb:any={};
getData(query): Observable<any> {
if(this.dataFromDb[query])
{
return Observable.of(this.dataFromDb[query]);
}
return this.http.get(query)
.map(res => res.json())
.do(res => this.dataFromDb[query] = res)
.catch(err => Observable.throw(err.json() || 'Błąd');
}
}