'Angular 8 - Upload file along with form and send to server

I have a reactive form in Angular 8. On form submission, I need to post the values along with the uploaded file to an API. But I am not quite sure of how to post the file along with values.

<form [formGroup]="detailsForm" (ngSubmit)="onSubmit()">
    <div>
        <label for="desc">Description</label>
        <input type="text" id="desc" formControlName="desc"/>
    </div>
    <div>
        <label for="summary">Summary</label>
        <textarea id="summary" formControlName="summary"></textarea>
    </div>
    <div formGroupName="contact">
        <div>
            <label for="name">Name</label>
            <input type="text" id="name" required formControlName="name"/>
        </div>
        <div>
            <label for="email">Email</label>
            <input type="email" id="email" formControlName="email"/>
        </div>
    </div>
    <div>
        <label for="file">Upload File</label>
        <input type="file" id="file" formControlName="file">
    </div>
    <button type="submit">Submit</button>
</form>

In component

constructor(public httpService: HttpRequestService) { }

onSubmit() {
   this.httpService.postData(this.detailsForm.value).then(
      (result) => {
        this.jsonResponse = result;
      },
      (err) => {
        this.errorResponse = 'Sorry, there was an error processing your request!'; 
      }
   )
}

In service

postData(detailsData) {
    const headers = new HttpHeaders(
      { 'Content-Type': 'multipart/form-data' }
    );
    return new Promise((resolve, reject) =>{
      this.http.post('http://localhost:3000/postData', detailsData, { headers: headers })
      .subscribe(res => resolve(res), err => reject(err))
    });
}

In backend, just for testing purpose

const express = require("express");
const bodyParser = require('body-parser');
const cors = require('cors');

const app = express();
app.use(cors());
// Configuring body parser middleware
app.use(bodyParser.urlencoded({ extended: false }));
app.use(bodyParser.json());

app.listen(3000, () => {
 console.log("Server running on port 3000");
});

app.post("/postData", (req,res) => {
    console.log(req.body);
});

All the values are logged, but for file I am only getting the path value. How do I get the contents of file(I need to upload and send Excel File).



Solution 1:[1]

Here's what I normally do when I want to send a file to the backend.

Html element

        <div class="form-group">
          <input style="color:transparent;" onchange="this.style.color = 'black';" type="file"
            (change)="onImageChange($event)" #bannerPhoto />
        </div>

component.ts

onImageChange(event) {
const reader = new FileReader();
if (event.target.files && event.target.files.length) {
  const [file] = event.target.files;
  reader.readAsDataURL(file);
  reader.onload = () => {
    this.addEventForm.patchValue({
      banner: reader.result
    });
    this.formData.append('banner', event.target.files[0], event.target.files[0].name);
  };
}}

Here's the type of formData and addEventForm variables:

  addEventForm: FormGroup;
  formData: FormData;

How I'm calling the API:

    this.eventService.add(this.formData)
        .subscribe(/*Your code goes here*/)

Solution 2:[2]

I recommend you to use Multer at the backend side, and create form data request because you have a one file at the request, this condition makes the request a little bit more difficult.

You can check the npm package from: Multer NPM

The next code was extracted and adapted from the page linked before:

const cpUpload = upload.fields([
    { name: 'excel_file', maxCount: 1 }, 
    { name: 'description', maxCount: 1 },
    { name: 'summary', maxCount: 1 },
    { name: 'name', maxCount: 1 },
    { name: 'email', maxCount: 1 }
])
app.post('/postData', cpUpload, function (req, res, next) {
  // req.files is an object (String -> Array) where fieldname is the key, and the value is array of files
  //
  // e.g.
  //  req.files['excel_file'][0] -> File
  //  req.files['excel_file'] -> Array but contains only 1 file
  //
  // req.body will contain the text fields, if there were any
  const file = req.files['excel_file'][0];
  const description = req.body.description;
  ...
})

On the client side, you first need to catch the file selected by the user:

@HostListener('change', ['$event.target.files'])
emitFiles( event: FileList ) {
    const file = event && event.item(0);

    if (file) {
        this.file = file; // this is a variable in the component
    }
}

The next step is to create the applicative FormGroup -> FormData, you can do the task by:

submit() {
    const formData = new FormData();
    formData.append("excel_file", this.file); // be careful, the names appended as a key in the form data must be the same of the defined fields at the backend.
    formData.append("description", this.detailsForm.get("description").value;
    // ... more appends from formGroup
    
    // time to send the request!
    this.httpClient.post(".../postData", formData).subscribe({
        next: okResponseFromServer => {
            // do anything
        },
        error: err => {
            // handle the error
        }
    })
}

Sources

This article follows the attribution requirements of Stack Overflow and is licensed under CC BY-SA 3.0.

Source: Stack Overflow

Solution Source
Solution 1
Solution 2 Adrian Yama Yama