21

I'm trying to send a form along with a file to my API through Angular 6, but the post doesn't include the file, even though the object that's supposed to be sent does.

When I'm looking at the console logs I see what is expected, amount:"amount", invoicefile: File.... But In the outgoing request the field shows invoicefile:{}, and now file is received on the other side. Some pictures are included at the end.

Lastly my API is telling my all fields are missing, but I think that another problem.

The component looks like this:

import { Component, OnInit } from '@angular/core';
import { Router } from '@angular/router';
import { first } from 'rxjs/operators';
import { FormGroup, FormBuilder, FormControl, Validators, FormArray, ReactiveFormsModule } from '@angular/forms';
import { HttpClient } from '@angular/common/http';

import { AlertService } from '../_services';
import { InvoiceService } from '../_services';
import { Invoice } from '../_models';

@Component({
  selector: 'app-registerinvoice',
  templateUrl: './registerinvoice.component.html',
  styleUrls: ['./registerinvoice.component.css']
})
export class RegisterinvoiceComponent implements OnInit {
  public registerForm: FormGroup;
  public submitted: boolean;

  constructor(
    private router: Router,
    private invoiceService: InvoiceService,
    private alertService: AlertService,
    private http: HttpClient,

  ) { }
  fileToUpload: File = null;

  ngOnInit() {
    this.registerForm = new FormGroup({
      serial: new FormControl('', [<any>Validators.required, <any>Validators.minLength(5)]),
      amount: new FormControl('', [<any>Validators.required, <any>Validators.minLength(4)]),
      debtor: new FormControl('', [<any>Validators.required, <any>Validators.minLength(10)]),
      dateout: new FormControl('', [<any>Validators.required, <any>Validators.minLength(8)]),
      expiration: new FormControl('', [<any>Validators.required, <any>Validators.minLength(8)]),
    });
  }
  handleFileInput(files: FileList){
    this.fileToUpload=files.item(0);
  }

  deliverForm(invoice: Invoice, isValid) {
    this.submitted=true;
    if (!isValid){
      return;
    }
    invoice.invoicefile=this.fileToUpload;
    console.log(invoice);
    console.log(typeof(invoice.invoicefile));
    this.invoiceService.create(invoice)
      .pipe(first())
      .subscribe(
        data => {
          this.alertService.success('Invoice successfully uploaded', true);
          this.router.navigate(['/profile']);
        },
        error => {
          this.alertService.error(error);
        });
  }

}

Followed by the service that provides the post:

import { Injectable } from '@angular/core';
import { HttpClient, HttpHeaders } from '@angular/common/http';
import { Http } from '@angular/http';
import { Invoice } from '../_models';
import { FormGroup } from '@angular/forms';

const HttpUploadOptions = {
  headers: new HttpHeaders({ "Content-Type": "multipart/form-data" })
}
@Injectable({
  providedIn: 'root'
})
export class InvoiceService {

  constructor(
    private http: HttpClient
  ) { }
  create(invoice: Invoice){
    return this.http.post('/api/v1/invoices/', invoice, HttpUploadOptions)
  }
}

And lastly the class:

export class Invoice {
    id: any;
    serial: any;
    amount: any;
    debtor: any;
    dateout: any;
    expiration: any;
    fid: any;
    invoicefile: File;
}

The console log that looks correct:enter image description here

And the outgoing request where the file is missing: enter image description here

EDIT:

Now the service code for create looks like this:

create(invoice: Invoice){
    let payload=new FormData();
    payload.append('amount', invoice.amount);
    payload.append('debtor', invoice.debtor);
    payload.append('serial', invoice.serial);
    payload.append('dateout', invoice.dateout);
    payload.append('expiration', invoice.expiration);
    payload.append('invoicefile', invoice.invoicefile);
    return this.http.post('/api/v1/invoices/', payload, HttpUploadOptions)
  }

And the response looks like this. Looks weird to me, and I'm still getting some errors from my back-end, but that's another question. enter image description here

Marcus Grass
  • 1,043
  • 2
  • 17
  • 38

5 Answers5

36

Your POST request body is actually JSON, not Multipart as you would hope (despite what the Content-Type header says).

In order to remedy that, you need to build a FormData object, and use that in your request instead:

let input = new FormData();
// Add your values in here
input.append('id', invoice.id);
input.append('invoiceFile', invoice.invoiceFile);
// etc, etc

this.http.post('/api/v1/invoices/', input, HttpUploadOptions)
user184994
  • 17,791
  • 1
  • 46
  • 52
  • I edited my code as you described, is a multipart response supposed to look like that? I'm trying to figure out if the problem is definitely with my back-end now. Since it's saying that all fields are missing, though, if that response is correct, that is another question for later. – Marcus Grass May 28 '18 at 20:09
  • Yes, that's better, that's the correct format now. Just make sure that your backend is set up to expect multipart as well – user184994 May 28 '18 at 20:11
  • For brevity I'll include that for this to work HttpUploadOptions need to be removed completely. If it is forced to be multipart/form-data the vitaly important WebKitFormBoundary isn't included, and it NEEDS to be included in the header, otherwise the back-end doesn't know where to cut the different form inputs, and will throw an error. – Marcus Grass May 29 '18 at 21:04
  • Thanks buddy it helps me lot. – Gurpreet Singh Jun 04 '19 at 19:08
31

Remove the multipart/form-data from the headers to fix this issue

const HttpUploadOptions = {
  headers: new HttpHeaders({ "Content-Type": "multipart/form-data" })
}

Solution

const HttpUploadOptions = {
  headers: new HttpHeaders({ "Accept": "application/json" })
}
user2617449
  • 348
  • 3
  • 9
3

I had previously this one which was giving error

const formData = new FormData();
formData.append(...);
this.http.post(apiUrl, {formData});

I just removed the object from braces and it worked

this.http.post(apiUrl, formData);
mbojko
  • 13,503
  • 1
  • 16
  • 26
AbdulRehman
  • 946
  • 9
  • 16
0

Folks,

if you are running out of options. you can also try to pass the content type as empty string as below:

const HttpUploadOptions = {
  headers: new HttpHeaders({ 'Content-Type':'' })
}

or

const HttpUploadOptions = {
  headers: new HttpHeaders().append({ 'Content-Type':'' });
}
0

For Angular v8+, you can try @ng-stack/forms, it have "file" support with validation feature.

You don't even need to add headers, just pass form value:

// Value of formControl here is instance of FormData
// and it's OK to directly upload this value.
const formData = this.formControl.value;

this.httpClient.post('api/path', formData).subscribe(() => {
  // Do something
});
ktretyak
  • 27,251
  • 11
  • 40
  • 63