97

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")?

jonrsharpe
  • 115,751
  • 26
  • 228
  • 437
Damon Kaswell
  • 1,270
  • 1
  • 10
  • 16
  • 1
    I think it's pretty lame that angular makes it so difficult to post as `www-form-urlencoded`. There should be first class support to do this easily. – goat Aug 31 '17 at 18:50
  • 1
    this doesn't seem to work anymore with Angular4+ and HttpClient. – dragonfly02 Dec 23 '17 at 22:24
  • You are correct, @stt106. This solution is old, and specific to earlier versions than 4. I will rewrite the solution above for HttpClient when I have time. – Damon Kaswell Dec 26 '17 at 17:12

12 Answers12

123

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:

  1. Use URLSearchParams for your body
  2. Convert body to string
  3. 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';

Mick
  • 8,203
  • 10
  • 44
  • 66
  • 1
    Guys watch out you need a polyfill! I added it to the answer. – Mick Sep 27 '17 at 10:31
  • 1
    Ugh, angular. It's insane that we need to write such verbose boilerplate code to do such a simple thing. – goat Nov 02 '17 at 22:03
  • 1
    Honestly i think that is pretty simple. What if you want to add more header options? Also the generation of x-www-form-urlencoded is automatically done by URLSearchParams. I dont see any boilerplate code at all here. If you do so, with the new beautiful Angular HttpClient you can create an [Interceptor](https://angular.io/guide/http#advanced-usage) and add your header options there. No duplicate code then. Ugh, Angular (is actually great!) – Mick Nov 03 '17 at 10:34
  • 7
    Shouldn't one use "new" Angular 5 `HttpParams` instead of "old" `URLSearchParams`? – superjos Feb 16 '18 at 17:04
  • Thanks, that was a puzzle! – Lyubomir Velchev Mar 07 '18 at 15:39
  • 2
    HttpParams works and avoids the polyfill, but it is immutable, so you have to use body = body.set('user', username); etc. – Wolfgang Stengel May 12 '18 at 23:32
112

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(...);
Brad
  • 4,493
  • 2
  • 17
  • 24
  • 4
    Tried the URLSearchParams() option, which didn't work, but you were absolutely right about the format of the body. That did the trick. Marked as the answer, and I'm going to update the post for other people to find. – Damon Kaswell Oct 05 '16 at 17:02
  • I'm using `URLSearchParams` as the body for form data and having no problems. I think it was introduced in RC4. What version of angular are you using? – Brad Oct 05 '16 at 22:13
  • Final, but looking back, I'm pretty sure I just typo'd it, and since the second option worked, I didn't bother to retry. Thanks again for the assist, by the way. – Damon Kaswell Oct 05 '16 at 22:38
  • 9
    @DamonKaswell No success using URLSearchParams as body, for me either. If I do that the content type is application/json and the json data posted is an empty object. I'm on Angular 4.0. I ended up doing this ` const headers = new Headers({ 'Content-Type': 'application/x-www-form-urlencoded' }); const options = new RequestOptions({ headers: headers }); const body: URLSearchParams = new URLSearchParams(); body.set('username', username); body.set('password', password); return this.http.post(API_URL + 'login', body.toString(), options)` – joensson Apr 28 '17 at 23:04
  • If you're building the body string manually, remember to encode your username and password, otherwise certain characters won't be passed to the server correctly... – Dawson Toth Nov 15 '17 at 05:57
  • doing this same thing with httpClient instead in angular 8 works. But doing that in angular 5 does not (the content type does not set). Meanwhile, by angular 5, the Http module is deprecated. How can we do this in angular 5 ? – roberto tomás Dec 20 '19 at 16:32
  • This solution is **deprecated**. See https://angular.io/guide/deprecations#angularhttp. You can use [**`HttpParams`**](https://angular.io/api/common/http/HttpParams) now, like explained in [Robert Hegner's answer](https://stackoverflow.com/a/54010503/9434800) – Mickael B. Jun 04 '20 at 20:36
  • you should remember about `${encodeURIComponent(username)}, otherwise it can throw 400 Bad Request when username or password contains special characters! – Aleksander Burzec Dec 21 '20 at 09:26
41

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!

Johan Kvint
  • 907
  • 1
  • 8
  • 17
33

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);
Robert Hegner
  • 9,014
  • 7
  • 62
  • 98
6

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.

juagicre
  • 1,065
  • 30
  • 42
arcesync
  • 61
  • 1
  • 1
5

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);
  }));

}

khaleel
  • 63
  • 1
  • 2
5

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);
  }
reynold adade
  • 74
  • 1
  • 3
5

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.

Kingston Fortune
  • 905
  • 8
  • 17
0

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!

Vainqueur
  • 21
  • 5
0

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 }
);
Brady Huang
  • 1,852
  • 20
  • 23
  • This does not work. HttpParams is immutable so using set like this won't work and give an empty body. – schankam Jun 01 '21 at 09:34
0
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)));
 }
0
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);
Tyler2P
  • 2,324
  • 26
  • 22
  • 31
  • 2
    Your answer could be improved with additional supporting information. Please [edit] to add further details, such as citations or documentation, so that others can confirm that your answer is correct. You can find more information on how to write good answers [in the help center](/help/how-to-answer). – Community Oct 03 '21 at 10:20