0

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).

hanik
  • 119
  • 6
  • 17
  • Here is explained https://w3path.com/new-angular-8-file-upload-or-image-upload/ – Reimond Feb 18 '20 at 09:40
  • multer is the package in the backend used for the file uploading functionality. Have a look at https://code.tutsplus.com/tutorials/file-upload-with-multer-in-node--cms-32088 – Kishore Chilakala Feb 18 '20 at 09:58

2 Answers2

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*/)
Mohamad Mousheimish
  • 1,641
  • 3
  • 16
  • 48
  • I am new to Angular. We are using the FileReader here, to read the file and we patch value on the FormGroup. Why are we doing so? Isn't it enough to append to formData. – hanik Feb 18 '20 at 10:57
  • I am getting an error on, this.addEventForm.patchValue({ banner: reader.result }); The error is ERROR DOMException: Failed to set the 'value' property on 'HTMLInputElement': This input element accepts a filename, which may only be programmatically set to the empty string. If I have only the file in my form it works fine. But when I add fields to the form I am getting the error. – hanik Feb 19 '20 at 08:35
  • Sorry for the late reply, as for appending it to the `FormGroup` I was appending it because I needed it for another thing. And as for the error, please check this answer: https://stackoverflow.com/a/41938495/9401556 – Mohamad Mousheimish Feb 19 '20 at 09:05
  • @NHG if you used my answer and it helped you, can you please mark it as answer. If not then its your choice. Thank you. – Mohamad Mousheimish Feb 19 '20 at 13:53
0

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
        }
    })
}