How to correctly attach file to formData before POSTing to server?

16,352

Set Content-Type: undefined

When posting objects created by the FormData API it is important to set the content type header to undefined.

const insightPDF = (formData) => {
    console.log(' formData', formData)
    var config = { headers: {'Content-Type': undefined} };
    return $http.post('/app/api/insights_pdf', formData, config)
     .then((res) => {
        console.log('PDF uploaded res', res)
        return res;
    }).catch(apiError);
};

Normally the AngularJS framework, automatically add the content type header as application/json which overrides the content type set by the XHR Send() method. When the XHR API sends a FormData object, it automatically sets the content type to multipart/form-data with the proper boundary.

From the Docs:

The $http service will automatically add certain HTTP headers to all requests

To explicitly remove a header automatically added via $httpProvider.defaults.headers on a per request basis, Use the headers property, setting the desired header to undefined. For example:

var req = {
 method: 'POST',
 url: 'http://example.com',
 headers: {
   'Content-Type': undefined
 },
 data: { test: 'test' }
}

— AngularJS $http Service API Reference - Setting HTTP Headers

FormData objects like blobs are host-defined objects non-native to JavaScript. Not all of their properties can be seen by console.log or console.dir. Files are a special type of blob. The data is not necessarily loaded from disk. Usually, the data is streamed from disk only when needed by a specific API.


Avoid Extra Overhead -- Send files directly

Content in multipart/form-data uses base64 encoding which adds 33% extra overhead. If uploading only one file, it is more efficient to send the file blob directly.

//MORE Efficient; Avoids base64 encoding overhead

const insightPDF = (dataObject) => {
    var config = { headers: {'Content-Type': undefined} };
    return $http.post('/app/api/insights_pdf', dataObject, config)
     .then((res) => {
        console.log('PDF uploaded res', res)
        return res;
    }).catch(apiError);
};

var file = inputElem[0].files[0];
insightPDF(file);

If the server can accept binary content directly, it is best to send files that way. The XHR API will automatically set the content type to that of the file.

Share:
16,352

Related videos on Youtube

Leon Gaban
Author by

Leon Gaban

Investor, Powerlifter, Crypto investor and global citizen You can also find me here: @leongaban | github | panga.ventures

Updated on May 25, 2022

Comments

  • Leon Gaban
    Leon Gaban almost 2 years

    I've been following this FormData tutorial here, however have yet to understand how the formData object works.

    My input form

    enter image description here

    <input type="file" id="file-id" class="w300px rounded4px" name="file" placeholder="PDF file">
    <button class="btn-upload-pdf" ng-click="asub.uploadPDF()">Upload</button>
    

    Here is the upload button function:

    this.uploadPDF = () => {
        const formData = new FormData();
        const fileInput = document.getElementById('file-id');
        const file = fileInput.files[0];
        formData.append('pdf-file', file);
        console.log('formData', formData)
    
        return ApiFactory.insightPDF(formData).then((res) => {
            console.log('res', res);
            return res;
        });
    };
    

    When I log out the fileInput object .files[0] I see the file I just attached:

    enter image description here

    It would seem to mean that this object should be enough to send along to the POST. However this is the next step:

    formData.append('pdf-file', file);

    I log out formData before I send it into my Factory and this is the result, I don't see the key pdf-file or that PDF anywhere, just a bunch of methods? Where does the file get appended too? How does formData contain the actual PDF?

    enter image description here

    I need to attach something from the formData object I presume:

    The Factory that makes the POST request

    const insightPDF = (formData) => {
        console.log(' formData', formData)
        return $http.post('/app/api/insights_pdf', formData).then((res) => {
            console.log('PDF uploaded res', res)
            return res;
        }).catch(apiError);
    };
    
    • jbrown
      jbrown over 7 years
      You should be posting fromData not file.
    • Leon Gaban
      Leon Gaban over 7 years
      Ah sorry, I am posting fromData, I just changed the var name to file when inside of the insightPDF function, I should probably change that back.
  • Leon Gaban
    Leon Gaban over 7 years
    Thanks! No more errors... now just need to finish the backend endpoint :) is there a way tho to see where the file is attached inside of the FormData object? When I log it out, it's still just a bunch of methods. What does the formData.append('pdf-file', file); do?
  • georgeawg
    georgeawg over 7 years
    As I said in my answer, the formData object is host-environment object. It is an exotic non-native proxy for things in the host environment. JavaScript can only see the methods and properties made available to the language. Under the hood there is more. Web APIs like FileReader and XHR send have connections under the hood to host environment methods and properties.
  • georgeawg
    georgeawg about 5 years
    To see what is attached inside the FormData object, use the formData.entries() method. For an example, see form data is not appending file object.