5

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);
};
georgeawg
  • 48,608
  • 13
  • 72
  • 95
Leon Gaban
  • 36,509
  • 115
  • 332
  • 529

1 Answers1

6

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.

georgeawg
  • 48,608
  • 13
  • 72
  • 95
  • 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? – Leon Gaban Feb 04 '17 at 22:15
  • 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 Feb 05 '17 at 03:28
  • To see what is attached inside the FormData object, use the [formData.entries()](https://developer.mozilla.org/en-US/docs/Web/API/FormData/entries) method. For an example, see [form data is not appending file object](https://stackoverflow.com/questions/55300330/form-data-is-not-appending-file-object/55306625#55306625). – georgeawg Mar 28 '19 at 04:15