I am using Angular 2.0 to write a custom HTTP Provider which allows me to attach a bearer token to each HTTP Request to the API. This is essentially what ADAL JS does, but I can not use that library in my application.
The problem is this - before I make a call to my HTTP API, I need to wait unit both tokens are present in session storage. Once I have both, I can then send the request.
My HTTP Client class looks like this:
import { Injectable } from '#angular/core';
import { Http, Headers } from '#angular/http';
function getWindow(): any {
return window;
}
#Injectable()
export class HttpClient {
private _window: Window;
constructor(private http: Http) { }
createAuthorizationHeader(headers: Headers) {
let keys = sessionStorage.getItem("adal.token.keys").split("|");
let key1 = keys[0]; // web
let key2 = keys[1]; // api
if (!key1 || !key2) {
// I NEED TO WAIT FOR BOTH KEYS!
}
let accessToken = sessionStorage.getItem("adal.access.token.key" + key2);
headers.append('Authorization', 'Bearer ' + accessToken);
}
get(url) {
let headers = new Headers();
this.createAuthorizationHeader(headers);
return this.http.get(url, {
headers: headers
});
}
post(url, data) {
let headers = new Headers();
this.createAuthorizationHeader(headers);
return this.http.post(url, data, {
headers: headers
});
}
}
I would like to avoid using a Timer (setTimeout) for obvious reasons. I would like to use an ES6 promise type of thing.
This is going to be a long question.
For windows authentication to work with angular I have wrapper for the http calls as shown below
import { Injectable } from '#angular/core';
import { Http, Response, RequestOptions, Headers, RequestOptionsArgs } from '#angular/http';
import { Config } from '../_helpers/config';
import { Observable } from 'rxjs/Observable';
#Injectable()
export class HTTPUtility {
public baseApiUrl: string;
constructor(private http: Http, private config: Config) {
this.baseApiUrl = this.config.getByKey('baseApiUrl') || '';
}
public getApiUrl(url) {
return this.baseApiUrl + url;
}
public get(url: string, options?: RequestOptions) {
if (!options) {
options = this.getDefaultHeaders();
}
return this.http.get(this.getApiUrl(url), options)
.catch(this.handleError);
}
private getDefaultHeaders() {
let headers = new Headers({ 'Content-Type': 'application/x-www-form-urlencoded' });
headers.append('Accept', 'application/json');
return new RequestOptions({ headers, withCredentials: true });
}
public handleError(response: Response) {
return Observable.throw(JSON.parse(response.json().Message) || 'Server error');
}
}
If you observe new RequestOptions({ headers, withCredentials: true }); is allowing browser to send credentials to server for windows authentication.
And it's working great for everything.
Now coming to the issue, I have sampleComponent in which i'm using ServerDataSource as shown below:
import { Component, OnInit, NgZone } from '#angular/core';
import { Http, RequestOptions } from '#angular/http';
import { Ng2SmartTableModule, ServerDataSource } from 'ng2-smart-table';
#Component({
selector: 'criteria',
templateUrl: './criteria.component.html',
styles: [require('./criteria.scss')],
})
export class SampleComponent implements OnInit {
Source: ServerDataSource;
settings: any;
constructor(
private http: Http) {
this.Source = new ServerDataSource(http, { endPoint: 'https://xxxxxx.org/yyy/GetCriterias'});
}
ngOnInit(): void {
// this.settings = {}// assigning the settings.
}
}
As you can see ServerDataSource is accepting Http instance and I have checked there documentation and haven't found any way to pass to RequestOptions. So the web api call made by ng2-smart-table fails with 401 status as credentials is not passed.
To resolve this issue I have made changes directly to ng2-smart-table source file to be specific 'server.data-source.js' and here is the change
ServerDataSource.prototype.createRequestOptions = function () {
var requestOptions = {withCredentials : true}; // this where I have added the withCredntials flag
requestOptions.params = new URLSearchParams();
requestOptions = this.addSortRequestOptions(requestOptions);
requestOptions = this.addFilterRequestOptions(requestOptions);
return this.addPagerRequestOptions(requestOptions);
};
With this change everything is working fine as of now.
But I could have issue in future, if we upgrade the package in that case I have to again make changes.
So if any one can help me to fix the issue in some other way please let me know.
Links: https://github.com/akveo/ng2-smart-table/blob/master/src/app/pages/examples/server/advanced-example-server.component.ts
Thanks.
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);
This is my demo code:
products.service.ts
getProducts(){
this.headers = new Headers();
this.headers.append("Content-Type", 'application/json');
this.headers.append("Authorization", 'Bearer ' + localStorage.getItem('id_token'));
return this.http.get('http://mydomain.azurewebsites.net/api/products',{headers:headers}).map(res => res.json().data);
}
products.component.ts
constructor(private productsService: ProductsService) { }
ngOnInit() {
this.productsService.getProducts().subscribe((res) => {
console.log(res);
});
}
Is it nescessary to import something in the ngModule decorator to consume a REST API or my code is wrong? I can get the desired data with Postman Chrome Extension but not with Angular 2 code.
I hope to have explained my problem well.
Update
These are the errors i get:
Sorry for making you waste your time.
This was the problem:
app.module.ts
providers: [
ProductsService,
// { provide: XHRBackend, useClass: InMemoryBackendService }, // in-mem server
// { provide: SEED_DATA, useClass: InMemoryDataService } // in-mem server data
]
After commenting the in-mem server and and the in-mem server data the problem dissapeared.
You're not setting the headers in the request. You declare the Headers object but you don't actually do anything with it.
You need to set them in the get function like this:
return this.http
.get('http://mydomain.azurewebsites.net/api/products', { headers: headers })
.map(res => res.json().data);
I'd suggest you use ngx-rest-ex: https://www.npmjs.com/package/ngx-rest-ex
npm i -S ngx-rest-ex
It's convenient, you just need to specify the corresponding decorator on top of the method and the return type, it will replace your method body. The return type can be either Promise or Observable depending on the HTTP METHOD annotation that you specified.
My demo code for your case:
import { Injectable, Injector } from '#angular/core';
import { BaseUrl, GET, RESTClient } from 'ngx-rest-ex';
import { Product } from './models';
#Injectable({
providedIn: 'root'
})
#BaseUrl('http://mydomain.azurewebsites.net/api/')
export class ApiService extends RESTClient {
constructor(injector: Injector) { super(injector); }
protected getDefaultHeaders() {
return {
'Content-Type': 'application/json',
'Authorization': 'Bearer ' + localStorage.getItem('id_token')
};
}
#GET('products')
getProducts(): Promise<Product[]> {
return;
}
}
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);
}