I have Angular Service which make some http requests, but I need to get headers for those requests from Promise. Here how it works right now, I convert my promise to Observable:
export class SomeService {
constructor(private http: HttpClient, private auth: AuthenticationService) {}
getInfo(id: string): Observable<any> {
return this.auth.getToken().pipe(mergeMap((token: any) => {
let httpOptions = {
headers: new HttpHeaders({
'Content-Type': 'application/json',
'Authorization': 'Bearer ' + token
})
}
return this.http.get(`${this.serverURL}/info`, httpOptions);
}))
}
}
getToken() {
return from(this.storage.get(TOKEN_KEY));
}
But obviously I have like 20-50 requests and it's not too good to fetch auth token with every request.
I want to fetch my token once and use it for all request. Also I have other header which comes from Promise I need to use in my request. So, how can I get my async headers once (probably in constructor) in this case?
First off consider if optimizing this code is actually needed. Optimizing for performance is often only useful in parts of code which are run very frequently. When you say you do some 20 to 50 requests it does not sound like it's used a lot (other parts of your app are probably a lot more cpu intensive).
That being said: if you still want to solve this you could indeed fetch the token in your constructor.
export class SomeService {
// We store the observable here for later use
private getTokenObservable: Observable<string>;
constructor(private http: HttpClient, private auth: AuthenticationService) {
// Retrieve the token now and store the observable
this.getTokenObservable = getToken();
}
getInfo(id: string): Observable<any> {
// Zip the two observables together
return zip(
// Re use the previously stored observable
this.getTokenObservable,
// Also request the last login
this.auth.getLastLogin()
).pipe(mergeMap((result) => {
const token = result[0];
const lastLogin = result[1];
let httpOptions = {
headers: new HttpHeaders({
'Content-Type': 'application/json',
'Authorization': 'Bearer ' + token
})
}
return this.http.get(`${this.serverURL}/info`, httpOptions);
}))
}
}
getToken() {
return from(this.storage.get(TOKEN_KEY));
}
This works because you can subscribe multiple times to the same observable. So we request and store the getToken observable only once and then re use it for each request.
Also note how we use the zip operator provided by rxjs. This allows us to merge two observables together so we can handle the result of both observables in a single function.
You can write separate service for getting the token and call the service only once. For next time when you need token check if already you have token value in variable of service, so can skip http request and simply return token to requester.
private _tokenObsevable = new BehaviorSubject<string>(null);
constructor(...) {
this.auth.getToken().subscribe(token => this._tokenObservable.next(token))
}
getInfo(...) {
this._tokenObservable.pipe(
switchMap(token => // rest the same
)
optional you can create getter like
get tokenObservable() {
return this._tokenObservable.pipe(filter(val => !!val))
}
in this case you get only non null values, but also if token won't appeare you'll get stuck
In this case you can create a utility function in a file and can import it everywhere you need this token, or can create a service if this token is coming form a server call and then can inject it in all the places you need this token.
This token can also be saved in a const files and imported in the place wherever required.
I've created a token-service.ts that calls my back end auth API which returns a JWT. I store this JWT in localstorage as shown here in my getToken():
getToken() {
this.http.post('myAuthEndpoint', { credentials })
.subscribe((res) => {
const token = res.headers.get('Authorization')
localStorage.setItem('id_token', token);
});
}
In my app.component.ts, I am calling the getToken() in my ngOnInit method.
However, here's what I have in my app.component.html:
<navigation-top></navigation-top>
<router-outlet></router-outlet>
And this is where I have an issue - In my NavigationTop component, I am calling my getNavigationTop() from my top-navigation.service.ts to populate the links and stuff. The API call I make in getNavigationTop() requires the auth token that I get in my getToken(), but its null on init.
How can I handle this case? Right now it works when I reload the page after the first load, because then I can get the value from localStorage:
getNavigationTop(): Observable<any> {
let headers = new Headers({ 'Authorization': localStorage.getItem('token') });
let options = new RequestOptions({ headers: headers });
let data = this.http
.get('my url', options)
.map((res: Response) => {
return res.json().navTop;
})
.catch(this.handleError);
return data;
}
Thanks
Move those to a service and add it into your main module as a provider.
Then do this below. You can then inject the service into any component and call them at will.
#Injectable()
export class TokenService {
getToken() {
this.http.post('myAuthEndpoint', { credentials })
.subscribe((res) => {
const token = res.headers.get('Authorization')
localStorage.setItem('id_token', token);
this.getNavigationTop(token);
});
}
getNavigationTop(token?): Observable<any> {
let headers = new Headers({ 'Authorization': token ? token: localStorage.getItem('token') });
and then in component:
import {TokenService} from '..../';
...
#Component
export class NavigationTop {
constructor(private tokenService: TokenService) {
}
ngOnInit() {
this.tokenService.getToken();
}
Now if you run getNavigationTop again later, it will check for token argument(optional) first but if none exist, try localstorage instead.
I have a project that needs to use Angular2 (final) to post to an old, legacy Tomcat 7 server providing a somewhat REST-ish API using .jsp pages.
This worked fine when the project was just a simple JQuery app performing AJAX requests. However, the scope of the project has grown such that it will need to be rewritten using a more modern framework. Angular2 looks fantastic for the job, with one exception: It refuses to perform POST requests using anything option but as form-data, which the API doesn't extract. The API expects everything to be urlencoded, relying on Java's request.getParameter("param") syntax to extract individual fields.
This is a snipped from my user.service.ts:
import { Injectable } from '#angular/core';
import { Headers, Response, Http, RequestOptions } from '#angular/http';
import { Observable } from 'rxjs/Observable';
import 'rxjs/add/operator/map';
#Injectable()
export class UserService {
private loggedIn = false;
private loginUrl = 'http://localhost:8080/mpadmin/api/login.jsp';
private headers = new Headers({'Content-Type': 'application/x-www-form-urlencoded'});
constructor(private http: Http) {}
login(username, password) {
return this.http.post(this.loginUrl, {'username': username, 'password': password}, this.headers)
.map((response: Response) => {
let user = response.json();
if (user) {
localStorage.setItem('currentUser', JSON.stringify(user));
}
}
);
}
}
No matter what I set the header content type to be, it always ends up arriving as non-encoded form-data. It's not honoring the header I'm setting.
Has anyone else encountered this? How do you go about forcing Angular2 to POST data in a format that can be read by an old Java API using request.getParameter("param")?
For Angular > 4.3 (New HTTPClient) use the following:
let body = new URLSearchParams();
body.set('user', username);
body.set('password', password);
let options = {
headers: new HttpHeaders().set('Content-Type', 'application/x-www-form-urlencoded')
};
this.http
.post('//yourUrl.com/login', body.toString(), options)
.subscribe(response => {
//...
});
Note 3 things to make it work as expected:
Use URLSearchParams for your body
Convert body to string
Set the header's content-type
Attention: Older browsers do need a polyfill! I used: npm i url-search-params-polyfill --save and then added to polyfills.ts: import 'url-search-params-polyfill';
UPDATE June 2020: This answer is 4 years old and no longer valid due to API changes in Angular. Please refer to more recent answers for the current version approach.
You can do this using URLSearchParams as the body of the request and angular will automatically set the content type to application/x-www-form-urlencoded and encode the body properly.
let body = new URLSearchParams();
body.set('username', username);
body.set('password', password);
this.http.post(this.loginUrl, body).map(...);
The reason it's not currently working for you is you're not encoding the body data in the correct format and you're not setting the header options correctly.
You need to encode the body like this:
let body = `username=${username}&password=${password}`;
You need to set the header options like this:
this.http.post(this.loginUrl, body, { headers: headers }).map(...);
For those still looking for an answer this is how I solved it with Angular 5 and HttpClient:
const formData = new FormData();
// append your data
formData.append('myKey1', 'some value 1');
formData.append('myKey1', 'some value 2');
formData.append('myKey3', true);
this.httpClient.post('apiPath', formData);
Do NOT set Content-Type header, angular will fix this for you!
This is what worked for me with Angular 7:
const payload = new HttpParams()
.set('username', username)
.set('password', password);
this.http.post(url, payload);
No need to explicitly set the header with this approach.
Note that the HttpParams object is immutable. So doing something like the following won't work, it will give you an empty body:
const payload = new HttpParams();
payload.set('username', username);
payload.set('password', password);
this.http.post(url, payload);
When using angular forms most parameters will be sent as objects, hence your login function will most likely have this object
form.value = {username: 'someone', password:'1234', grant_type: 'password'} as the parameter
to send this object as x-www-form-urlencoded your code will be
export class AuthService {
private headers = new HttpHeaders(
{
'Content-Type': 'application/x-www-form-urlencoded',
Accept: '*/*',
}
);
constructor(private http: HttpClient) { }
login(data): Observable<any> {
const body = new HttpParams({fromObject: data});
const options = { headers: this.headers};
return this.http.post(`${environment.baseUrl}/token`, body.toString(), options);
}
Angular 9
This is a code that works.
Take other options that fit to you to return not success answer.
let params = new HttpParams({
fromObject: { email: usuario.email, password: usuario.password, role: usuario.role },
});
let httpOptions = {
headers: new HttpHeaders({ 'Content-Type': 'application/x-www-form-urlencoded' }),
};
return this.http.post(`${this.url}/usuario/signup`, params.toString(), httpOptions).pipe(
map(
(resp) => {
console.log('return http', resp);
return resp;
},
(error) => {
console.log('return http error', error);
return error;
}
)
);
remember from string you use fromString and not fromObject.
I found out this solution after working several hours on this issue
login(userName: string, password: string) {
const headers = new Headers();
headers.append('Accept', 'application/json');
headers.append('Content-Type', 'application/x-www-form-urlencoded');
headers.append( 'No-Auth', 'True');
const body = new URLSearchParams();
body.set('username', userName);
body.set('password', password);
body.set('grant_type', 'password');
return this.http.post(
this.baseUrl + '/token'
, body.toString()
, { headers: headers }
)
.pipe(map(res => res.json()))
.pipe(map(res => {
localStorage.setItem('auth_token', res.auth_token);
return true;
}))
.pipe(catchError((error: any) => {
return Observable.throw(error);
}));
}
For Angular 12, this is what worked for me.
options = {
headers: new HttpHeaders().set('Content-Type', 'application/x-www-form-urlencoded')
};
params = new HttpParams()
.set("client_id", "client_id")
.set("client_secret", "client_secret")
.set("grant_type", "grant_type")
.set("scope", "scope")
getToken(){
return this._http.post(`${URL}`, this.params, this.options)
}
Also, remember to import the following at the top import { HttpClient, HttpHeaders, HttpParams } from '#angular/common/http';
Also notice that, unlike the others, we do not use toString() as it's redundant.
Guys I've been working on this since a while and thanks to this post from Josh Morony https://www.joshmorony.com/integrating-an-ionic-application-with-a-nodejs-backend/ I figured out what the problem was. Basically, when I started testing my api I was using POSTMAN and it was working perfectly but when it came to implementing it with Ionic Angular it became a problem. The solution in this post is only about importing body-parser and use it as app middleware like this app.use(bodyParser.json()) on your server-side root file(index).
Hopefully, this will help, Thanks!
Angular 8
const headers = new HttpHeaders({
'Content-Type': 'application/x-www-form-urlencoded'
});
const params = new HttpParams();
params.set('username', 'username');
params.set('password', 'password');
this.http.post(
'https://localhost:5000/api',
params.toString(),
{ headers }
);
export class MaintenanceService {
constructor(private http: HttpClient) { }
//header de requete http
private headers = new HttpHeaders(
{ 'Content-Type': 'application/x-www-form-urlencoded' }
);
// requete http pour recuperer le type des maintenances
createMaintenance(data: createMaintenance){
const options = { headers: this.headers};
return this.http.post('api/v2/admin/maintenances', data, options ).subscribe(status=> console.log(JSON.stringify(status)));
}
let options = {
headers: new HttpHeaders().set('Content-Type', 'application/x-www-form-urlencoded')
};
let body = new URLSearchParams();
body.set('userId', userId);
body.set('discussionId', discussionId);
I am experimenting with Angular 2 and trying to use document.cookie as my access-token storage.
What I have done is I set a cookie with an accesstoken & expiration of 30 mins. Now the application will set the cookie on load so subsequent api process handle pretty well till 30 mins. My problem is i want to check if cookies is expired and then fetch new accesstoken set it to the cookie and call respective method.
Here's my code
import { Injectable } from '#angular/core';
import { Http, Response, Headers } from '#angular/http';
import { Observable, Operator} from 'rxjs/Rx';
import { AccessToken } from './access-token.service';
#Injectable()
export class DataService {
constructor(
private _accessToken : AccessToken,
private _http: Http
) {}
request(url:string){
var token = this._accessToken.readCookie();
return this._http.get(url,{
headers : new Headers({
"Authorization" : "Bearer " + token
})
})
.map(data => data.json().result)
.catch(this._handleError)
}
fetchData(dataurl:string){
var token = this._accessToken.readCookie();
if(!token){
this._accessToken.getToken()
.subscribe(
response => {
this._accessToken.setCookie(response);
}
)
// I want to call this.request() after the accesstoken has been set to the document.cookie
} else {
return this.request(dataurl)
}
}
getAllProducts(){
return this.fetchData('https://localhost/api/products');
}
private _handleError (error: Response) {
console.error(error);
return Observable.throw(error.json().error || 'Server error');
}
}
The this._accessToken.getToken() method make a http.post request to an endpoint and gets an accesstoken.
The this._accessToken.setCookie() method then sets a document.cookie with the accesstoken and expiration time of 30 mins.
I'm not sure how i go about sequencing the fetchData() method incase of cookie expiration so that it first gets accesstoken sets it to cookie then only calls the request method.
Thanks
just move the call inside the subscribe(...) callback
if(!token){
this._accessToken.getToken()
.subscribe(
response => {
this._accessToken.setCookie(response);
// I want to call this.request() after the accesstoken has been set to the document.cookie
}
)
} else {
I am using angular 2 to make a get request and a post request. My get request works fine, but for my post, I do not see the request is made when checking my Firebug Net panel.
The code methods look like follows. I also have subscribe methods invoking them from a component class.
import {Injectable} from "angular2/core";
import {Http, Response, Headers, RequestOptions, Jsonp, URLSearchParams} from "angular2/http";
import { Observable } from 'rxjs/Observable';
import 'rxjs/add/operator/map';
#Injectable()
export class MyService{
constructor (private _http: Http){}
testPost(){
var json = JSON.stringify({"userId": 111, "givenName": "CZ"});
var body = "json="+json;
var headers = new Headers({ 'Content-Type': 'application/json' });
var options = new RequestOptions({ headers: headers });
return this._http.post("http://mylocal.post.url", body, options)
.map(this.extractData)
.catch(this.handleError);
}
private extractData(res: Response) {
alert("test whether this method is reached");
let body = res.json();
return body.data || { };
}
private handleError (error: any) {
alert("test whether this method is reached");
let errMsg = (error.message) ? error.message :
error.status ? `${error.status} - ${error.statusText}` : 'Server error';
console.error(errMsg); // log to console instead
return Observable.throw(errMsg);
}
//testGet method is running well
testGet(link:string){
return this._http.get(link)
.map(res => res);
}
}
my subscribing method onTestPost(), which is assigned to a button on the page.
onTestPost(){
this.myService.testPost()
.subscribe(
data => this.getData = JSON.stringify(data),
error => alert(error),
() => console.log("Finished")
);
}
I put an alert statement at the beginning of two helper methods. None of the alerts is reached. And I don't see any request called to the my local post url when debugging with Firebug.
While my testGet method works correctly, I just don't know what is missing for the testPost.
I think your subscribe methods are the issue here. Please make sure subscribe is called.
"This observable is cold which means the request won't go out until
something subscribes to the observable."
See https://angular.io/docs/ts/latest/guide/server-communication.html
testPost() : Observable <Response> //import{Response} from '#angular/http'
{
var json = JSON.stringify({"userId": 111, "givenName": "CZ"});
var headers = new Headers({ 'Content-Type': 'application/json' });
var options = new RequestOptions({ headers: headers });
return this._http.post("http://mylocal.post.url/api",JSON.stringify(json), options)
.map((res:Response)=>res.json())
.catch(this.handleError);
}