How can I tell Angular 2 to stop setting Content-Type headers automatically?

6.1k Views Asked by At

I am currently trying to access an API with an Angular 2 (via Ionic 2) app. Our laravel backend is set up in such a way that it expects an Accept header with the content-type of the request, and no Content-Type header. Not just an empty Content-Type header, but none at all.

So, I am setting up the requisite parameters in Http, omitting Content-Type in the Headers, and that's where the problem starts: Angular 2 apparently can't keep its hands off the Content-Type. If I give it an object as the body of the request, it sets the Content-Type to application/json. Which is still understandable. Then I stringify the Object, which causes Angular to set the Content-Type to text/plain. Attempts, via

headers = new Header({Accept: 'application/json', Content-Type: undefined});

or

headers.append("Content-Type", undefined);

or any other combination of any header I could imagine that contains an Accept of application/json and nothing else, even headers.set or headers.delete fails, Angular 2 keeps just doing its thing.

Setting Content-Type to undefined was suggested elsewhere, I have also tried to set it to an empty string, to application/json when passing in the stringified JSON, Angular just doesn't give a flying f... about what I want. Is there a way to turn this automatism off? Am I still doing something wrong (I am importing Headers, which, aka the lack thereof, was a problem elsewhere, so that should be out)?

Here comes the code:

import { Injectable, Inject } from '@angular/core';

import { Http, Headers, RequestOptions } from '@angular/http';
import 'rxjs/add/operator/toPromise';

import { ConfsService } from '../confs/confs.service';

import { ApiRequestBody }   from './api.request.body';

@Injectable()
export class ApiService {
    constructor(public confs: ConfsService, private apiRequestBody: ApiRequestBody, private http: Http) {

    post (method: string, data: any):Promise<any> {
        const api = this.confs.get().api;
        const url = api['server'] + "" + api[method];
        let allParams = this.apiRequestBody.toJSON(data);
        let body = JSON.stringify(allParams);
        let headers = new Headers({
            'Accept': 'application/json',
            'Content-Type': undefined,
        });
        let options = new RequestOptions({ headers: headers });     
        let obj = this.http.post(url, body, options).toPromise();
        return obj;
    }
}

The ConfsService just gets a couple of configuration parameters, aka the api server URL, and ApiRequestBody gets a service that creates a standard set of parameters that the API needs to even look at the request, in addition to those that are passed in via the data parameter (which is then merged into the standard parameters in the toJSON method) - no rocket science. And I am doing a toPromise() because I find a promise in this particular case easier to handle.

2

There are 2 best solutions below

2
On BEST ANSWER

After digging through Angular source code I have concluded that this is not possible. See static_request.ts (https://github.com/angular/angular/blob/5293794316cc1b0f57d5d88b3fefdf6ae29d0d97/packages/http/src/static_request.ts), it will first check if you have manually set the headers to a specific string, and undefined or empty string will fall through to the detectContentTypeFromBody function which will only set ContentType.NONE if your request body is null.

  /**
   * Returns the content type enum based on header options.
   */
  detectContentType(): ContentType {
    switch (this.headers.get('content-type')) {
      case 'application/json':
        return ContentType.JSON;
      case 'application/x-www-form-urlencoded':
        return ContentType.FORM;
      case 'multipart/form-data':
        return ContentType.FORM_DATA;
      case 'text/plain':
      case 'text/html':
        return ContentType.TEXT;
      case 'application/octet-stream':
        return this._body instanceof ArrayBuffer ? ContentType.ARRAY_BUFFER : ContentType.BLOB;
      default:
        return this.detectContentTypeFromBody();
    }
  }

  /**
   * Returns the content type of request's body based on its type.
   */
  detectContentTypeFromBody(): ContentType {
    if (this._body == null) {
      return ContentType.NONE;
    } else if (this._body instanceof URLSearchParams) {
      return ContentType.FORM;
    } else if (this._body instanceof FormData) {
      return ContentType.FORM_DATA;
    } else if (this._body instanceof Blob) {
      return ContentType.BLOB;
    } else if (this._body instanceof ArrayBuffer) {
      return ContentType.ARRAY_BUFFER;
    } else if (this._body && typeof this._body === 'object') {
      return ContentType.JSON;
    } else {
      return ContentType.TEXT;
    }
  }

Update:

It looks like it actually is possible by extending Request and overloading the detectContentType function to return 0. This requires accessing non public code though so it could break in the future:

import {Http, Headers, RequestOptions, RequestMethod, Request, BaseRequestOptions, Response} from '@angular/http';
import { ContentType } from '@angular/http/src/enums';
import { RequestArgs } from '@angular/http/src/interfaces';

class NoContentRequest extends Request {
  constructor(args: RequestArgs) {
    super(args);
  }
  detectContentType(): ContentType {
    return 0;
  }
}

const headers = new Headers({Accept: 'application/json'});

const request  = new NoContentRequest({
  method: RequestMethod.Post,
  url: 'https://www.example.com/example',
  responseType: ResponseContentType.Json,
  headers: headers,
  body: body
});

this._http.request(request)
  .catch((error: Response | any) => { console.error(error); return Observable.throw(error); })
  .first()
  .subscribe();
0
On

I think the better way is to delete 'Content-Type' header from request.

i.e

     this.http.post(apiUrl, request, {
         headers: new HttpHeaders().delete('Content-Type')
     });