In my Angular 2 application, I'm trying to use Http (#angular/http) to make requests to my API. For these requests to work, I need certain headers to be added to every request I make to the API (including a JWT header).
What I'd like to do is have an API class that takes care of creating the Http requests and some error handling and validation etc.
As it turns out, however, I cannot use the Http class from my API class, as it will come up with the following error;
user.service.ts
import { Injectable } from '#angular/core';
import {User} from "../models/User";
import {API} from "../API";
import {Http} from "#angular/http";
#Injectable()
export class UserService
{
constructor (private http : Http) {}
getProfile (user : User)
{
let api = new API (this.http);
return api.doRequest ('/user/' + user.id + '/profile');
}
}
API.ts
import {Http, Headers, RequestOptions} from '#angular/http';
export class API
{
...
constructor (private http : Http) {}
doRequest (url : string, method : string, data?)
{
let headers = {...};
let options = new RequestOptions ({ headers: new Headers (headers), ... } );
return this.http.get (url, data, options)
.catch ((error) => { ... } );
}
}
Things work better when using Http straight from the UserService, however.
Is there a way to fix this, or perhaps a better way to achieve the desired result? Should I just extend Http?
You should be using append() method to add headers and then pass it to request object as below
doRequest (url : string, method : string, data?)
{
headers= new Headers();
headers.append(name1,value1);
headers.append(name2,value2);
....
let options = new RequestOptions ({ headers: headers, ... } );
return this.http.get (url, data, options)
.catch ((error) => { ... } );
}
That's the way today setting HTTP headers (Angular > 4):
Import:
import {HttpClient, HttpHeaders} from '#angular/common/http';
and usage:
const headers = new HttpHeaders()
.set("X-CustomHeader", "custom header value");
Notice that we are building the headers object by chaining successive set() methods. This is because HttpHeaders is immutable, and its API methods do not cause object mutation.
Instead, a call to set will return a new HttpHeaders object containing the new value properties. So this means that the following will NOT work:
const headers = new HttpHeaders ();
headers.set("X-CustomHeader", "custom header value")
Related
Closed. This question is opinion-based. It is not currently accepting answers.
Want to improve this question? Update the question so it can be answered with facts and citations by editing this post.
Closed 1 year ago.
Improve this question
I need an advice how to make my code better.
I have a simple class that gets data from backend that is using jwt token auth.
export class RepositoryService {
constructor(private http: HttpClient, private envUrl: EnvironmentUrlService) { }
public getData = (route: string) => {
return this.http.get(this.createCompleteRoute(route, this.envUrl.urlAddress), this.generateHeaders());
}
private createCompleteRoute = (route: string, envAddress: string) => {
return `${envAddress}/${route}`;
}
private generateHeaders = () => {
return {
headers: new HttpHeaders({
"Content-Type": "application/json",
"Authorization": `Bearer ${localStorage.getItem("token")}`
}),
};
};
It works fine but the problem starts when I get a lot more of http methods. How can I change createCompleteRoute so I won't have to use generateHeaders() in every http method?
I though about doing something like:
private createCompleteRoute = (route: string, envAddress: string) => {
return `${envAddress}/${route}`, this.generateHeaders();
}
so http methods could look like this:
public getData = (route: string) => {
return this.http.get(this.createCompleteRoute(route, this.envUrl.urlAddress));
}
But have no idea how to write a valid function.
The best way to do what you ask for, could be to bring your logic for creating headers to an interceptor, which is going to automatically add the header parameters to every http call.
It could be something like this:
Your interceptor file (is kinda service, but have to implement HttpInterceptor:
import { Injectable } from '#angular/core';
import {
HttpRequest,
HttpHandler,
HttpEvent,
HttpInterceptor,
} from '#angular/common/http';
// The service/way you use to get your token
import { AuthService } from '../services/auth.service';
#Injectable()
export class MyInterceptor implements HttpInterceptor {
constructor(private authService: AuthService) {}
intercept(
request: HttpRequest<any>,
next: HttpHandler
): Observable<HttpEvent<any>> {
const url="\yourAPI\endpoint";
// Get your token
cont myToken = this.authService.getToken(); // or localStorage.getItem("token") or whatever your way to get your token
// Add authorization header with token if available
if (myToken) {
request = request.clone({
setHeaders: {
Authorization: `Bearer ${currentUser.user.api_token}`,
'Content-Type': 'application/json',
},
url,
});
}
...
}
EXTRA: More info about how to adding and updating headers and how to Use the interceptor for Intercepting requests and responses:
Adding & Updating Headers
Intercepting request & responses
I'm using plain javascript to fetch data from php scripts server-side but I'd like to try it out using angular.
This code fetches a php file that in turn queries a database (simple select with filter, etc) and returns a json file to be used by the script and then displayed.
Is there a simple way of doing this with angular?
This is the script as it is now
fetch('service/select.php')
.then(function(response) {
return response.json();
})
.then(function(data) {
//do something with the data
});
and this is the php file it fetches:
<?php
require_once("config.php");
mysqli_set_charset($con, 'utf8mb4');
mysqli_query($con, "SET NAMES 'utf8mb4'");
$rs = mysqli_query($con, "select * from names");
while($row = mysqli_fetch_assoc($rs)) {
$res[] = $row;
}
echo json_encode($res, JSON_UNESCAPED_UNICODE);
?>
(I know the php file is vulnerable to sql injection, its just an example file to quickly query data, not used in production)
Demo HTTPClient module is your need
import {Injectable} from '#angular/core';
import { HttpClient, HttpHeaders } from '#angular/common/http';
import { Observable, throwError } from 'rxjs';
import { retry, catchError } from 'rxjs/operators';
const httpOptions = {
headers: new HttpHeaders({
'Content-Type': 'application/json'
})
};
#Injectable()
export class DataService{
constructor(private http:HttpClient){ }
Post(url:string,body:any): Observable<any>{
return this.http.post<any>(url, body, httpOptions).pipe( retry(1), catchError(this.handleError) );
}
Get(url:string): Observable<any>{
return this.http .get<any>(url, httpOptions).pipe( retry(1), catchError(this.handleError) );
}
private handleError(error: any){
let body = error.json();
return body || {};
}
}
Angular provides HttpClient API to do HTTP requests. The response type of this API is Observable type of RxJS which has lots of built-in methods to
process your data.
You can do your HTTP request code as following in the angular way instead of fetch API.
const url = 'service/select.php';
const hdrs = new HttpHeaders({ 'Accept': accept ? accept : 'application/json; charset=utf-8' });
this.http.get(url, { headers: hdrs, observe: 'body', responseType: 'json'})
.subscribe(
data => // do whatever you want to do your data
err => // get the error response here
);
I'm building the front-end of an application in Angular 8. This application uses an OAuth 2 implementation to manage authentication (password grant) so any HTTP request (with the exception of ones to the token endpoint) needs to have on its header a valid access_token.
To provide said token I've made an Angular interceptor that retrieve the token from another service and then attach it to the intercepted HTTP request. The token retrieval method doesn't give directly the token but an observable which eventually resolves to a valid token, I made this choice because the access token may not be instantly available, if the token is expired the application needs to refresh it with an HTTP call and then the refreshed token can be passed to the HTTP interceptor.
The problem which I encounter is that despite my many attempts the interceptor doesn't wait for the token to be retrieved so at the end the interceptor is skipped and the HTTP request is made without any token attached.
This is the code of my interceptor, retrieveValidToken is the Observable which returns the token.
import { Injectable } from '#angular/core';
import { HttpInterceptor, HttpRequest, HttpHandler, HttpEvent } from '#angular/common/http';
import { FacadeService } from './facade.service';
import { Observable } from 'rxjs';
#Injectable({
providedIn: 'root'
})
export class HttpInterceptorService implements HttpInterceptor {
constructor(private facadeService: FacadeService) { }
intercept(req: HttpRequest<any>, next: HttpHandler): Observable<HttpEvent<any>> {
if (req.url.includes('localhost:3000') && !req.url.endsWith('token')) {
this.facadeService.retrieveValidToken()
.subscribe(
(res) => {
const clone = req.clone({ setHeaders: { Authorization: `Bearer ${res}` } });
return next.handle(clone);
},
(err) => {
const clone = req.clone({ setHeaders: { Authorization: `Bearer ` } });
return next.handle(clone);
}
);
} else {
return next.handle(req);
}
}
}
Observables are asynchronous. The code outside the subscribe method will not wait for the code inside.
You should return observable by itself, not only result inside its subscription:
intercept(req: HttpRequest<any>, next: HttpHandler): Observable<HttpEvent<any>> {
if (req.url.includes('localhost:3000') && !req.url.endsWith('token')) {
return this.facadeService.retrieveValidToken()
.subscribe(
res => {
const clone = req.clone({ setHeaders: { Authorization: `Bearer ${res}` } });
return next.handle(clone);
}
);
} else {
return next.handle(req);
}
}
Something similar:
How use async service into angular httpClient interceptor
The problem is that 'intercept' method should return observable immediately, so instead of subscribing to 'this.facadeService.retrieveValidToken()' use the following code:
return this.facadeService.retrieveValidToken().pipe(
mergeMap(token =>
next.handle(req.clone({ setHeaders: { Authorization: 'Bearer ${token}' }))
)
)
In main.js
import axios from 'axios';
axios.defaults.headers.common = {
'Authorization': 'JWT ' + Vue.auth.getToken()
};
axios.defaults.baseURL = process.env.VUE_APP_BASE_URL; //TODO: append the trailing slash
// Add modified axios instance to Vue prototype so that to be available globally via Vue instance
Vue.prototype.$http = axios;
Everything works fine up to this point. (I am able to successfully login and store the token)
Now, I have another component that fetches a list of users from the server through an ajax call performed on component’s created() lifehook.
My problem is that when I am trying to access this.$http in component I get back a 401 error response from the server because Authorization header is not available to the request headers (although I have pre-configured axios.defaults.headers.common)
The strange thing is that if I hit the refresh button on my browser then the token is correctly attached to the request header and the list of users is successfully fetched**.**
Could anyone please explain to me why is that happening?
You can user axios request interceptors to add your headers globally
axios.interceptors.request.use(function (config) {
// Do something before request is sent
return config;
}, function (error) {
// Do something with request error
return Promise.reject(error);
});
you can access your current request readers using config.header and you can set the headers to the request like
config.headers = {
'Authorization': 'JWT ' + Vue.auth.getToken()
}
https://github.com/axios/axios
You can create a class to add the headers of your choice globally.
import axios from 'axios';
/**
* A wrapper around an axios instance, preconfigured to have a base URL and auth headers
*/
class Axios {
constructor(baseUrl, bearerToken) {
return axios.create({
baseURL: baseUrl,
headers: {
Authorization: `Bearer ${bearerToken}`
}
});
}
}
export default Axios;
Then in your app.js
import { Axios } from 'my/class'
const myService = new Axios('baseURL', 'bearerToken');
Have you tried using asios.create?
http/index.js:
import axios from 'axios'
import env from '../config/env'
import store from '../store'
export default (() =>
axios.create({
baseURL: env.API_HOST,
headers: {
common: {
Authorization: store.getters['user/getAuthToken']
}
}
}))()
main.js:
import http from './http'
Vue.prototype.$http = http
Additionally I use a store action to update the axios client:
updateHTTPClientAuthToken () {
Vue.prototype.$http.defaults.headers.common.Authorization = this.getters.getAuthToken
}
I am taking the field of a form and passing it to a service as this.form.value when I am logging this.form.value on the console I am getting Object { email: "zxzx", password: "zxzxx" } when I am sending the same thing to the service and calling the server like :
import {Http} from 'angular2/http';
import 'rxjs/add/operator/map';
import 'rxjs/add/operator/toPromise';
import {Injectable} from 'angular2/core'
import {Post} from './post';
import {Observable} from 'rxjs/Observable';
#Injectable()
export class PostService {
//dependency injection
private _url = "http://127.0.0.1/accounts/login_user/";
constructor(private _http:Http) {
}
createPost(post){
return this._http.post(this._url,JSON.stringify(post))
.map(res=>res.json());
}
}
The server is being called but the values are not being passed. When I am logging the response on the console I am getting :
Object { _isScalar: false, source: Object, operator: Object }
Can somebody please help me solve this issue?
Thank you.
Your console.log prints the observable corresponding to your request but not its result. If you want to print this result, you can use the do operator:
createPost(post){
return this._http.post(this._url,JSON.stringify(post))
.map(res=>res.json())
.do(data => {
console.log(data);
});
}
You said that the request is executed. It's actually the case if you subscribe on the observable:
this.service.createPost(...).subscribe(() => {
(...)
});
Edit
You also need to set the Content-Type header:
createPost(post){
var headers = new Headers();
headers.append('Content-Type', 'application/json');
return this._http.post(this._url,JSON.stringify(post), { headers })
.map(res=>res.json())
.do(data => {
console.log(data);
});
}
Edit2
If you want to send an url-encoded form:
You also need to set the Content-Type header:
createPost(post){
var headers = new Headers();
headers.append('Content-Type', 'application/x-www-form-urlencoded');
let content = new URLSearchParams();
content.set('prop', post.prop);
(...)
return this._http.post(this._url, content.toString(), { headers })
.map(res=>res.json())
.do(data => {
console.log(data);
});
}
You need to subscribe() otherwise the observable won't do anything:
createPost(post){
return this._http.post(this._url,JSON.stringify(post))
.map(res=>res.json())
.do(val => console.log(val));
}
...
this.createPost(...).subscribe(data => console.log(data));