Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Getting octet-stream instead of image in node js

In my front-end I use angular6 and I have this form where you can choose an image either by dropping a file in a div or clicking the div to open a file picker.

The form is

<form [formGroup]="imageUpload" (ngSubmit)="imageUploadSubmitted($event.target)"  > 
  <div id="imageDrop" (click)='imageInput.click()' (drop)="drop($event)" (dragover)="allowDrop($event)" #imageDrop></div> 
  <input type="file"  (change)='imageChange($event)' #imageInput id="imageInput" name = 'imageInput'  accept=".png, .jpg, .jpeg, .gif" formControlName="imageInput"  required  > 
  <button type="submit" >Submit</button>  
</form>

This is the typescript

  selectedFile:File=null;

  allowDrop(e) {
    e.preventDefault();
  }

  drop(e) {    
    e.preventDefault();  
    this.imageUpload.controls.imageInput.reset();  
    this.selectedFile = e.dataTransfer.files[0];
    let input = this.imageUpload.controls.imageInput as any;        
    input.value = e.dataTransfer.files[0];     
  }

  imageChange(e){            
    this.selectedFile = e.target.files[0];
  }

So, when dropping an image, get it from the event and put it in the file input. When the form is submitted, send the form data to a service for posting. The console.log shows a File object (__proto__ : File) whether you picked an image from the file picker, or dropped one in the div.

  imageUploadSubmitted(form){
    console.log('imageInput value - ', this.imageUpload.controls.imageInput.value);           
    this.mapcmsService.uploadImage(form).subscribe((data) =>{
      if(data.success){   alert('all good'); }
      else{ alert('error');  }
    })
  }

The service grabs the form controls and builds a FormData object to send in node.

  uploadImage(data){
    let formData = new FormData(data);
    let headers = new Headers();
    headers.append('Authorization',this.userToken);
    return this.http.post('http://localhost:3000/user/upload/image', formData  ,{headers:headers}  ).pipe(map(res => res.json()));  
  }

In node I use formidable to get the file and save it. This is for testing.

  var form = new formidable.IncomingForm();

  form.parse(req);
  form.on('file', function (name, file){
    console.log('file' , file);
  });

The problem is that if I have chose an image via the file picker, I get a file of type image/jpeg , a name a path and a size.

If I chose an image by drag and drop, I get a file of type application/octet-stream. This has no name and no size.

I would like to get image/jpeg in both cases. I am confused, is this a node or angular issue? How can I fix this ?

Thanks

angular 6 , node 8.11.1, formidable 1.2.1

like image 889
slevin Avatar asked Sep 24 '18 18:09

slevin


1 Answers

The issue is that the assignment in the drop event does not actually set the value of the input because file inputs are not supported by Angular reactive forms. I am talking about this lines:

let input = this.imageUpload.controls.imageInput as any;        
input.value = e.dataTransfer.files[0];

So when you drop in your case you are not actually sending the file to the server at all. That is why the data you get is wrong. Here are also links to two other stack overflow questions about reactive forms and file upload where there is more information regarding this issue.

  • How to include a file upload control in an Angular2 reactive form?
  • Using reactive form validation with input type=“file” for an Angular app

There are two possible solutions to workaround this issue. The first is that you get the ElementRef of the file input by using the ViewChild query. And then assign the files to the native html element directly. The good thing with this approach is that you will see the dropped file name also in the file input. The downside is that this might not work in all browsers. The official documentation on MDN says that it works in modern browsers but for me it did work in Chrome and not in Edge. Here is a sample of the code:

@ViewChild('imageInput') private imageInput: ElementRef;

public drop(e: DragEvent) {    
    e.preventDefault();
    this.imageUpload.controls.imageInput.reset();  
    this.selectedFile = e.dataTransfer.files[0];
    this.imageInput.nativeElement.files = e.dataTransfer.files;
  }

The other approach is that you build the FormData object yourself by adding the selected file yourself in code before sending it to the server. This should work anywhere without issues. Here is a sample code:

let formData = new FormData();
formData.append('imageInput', this.selectedFile);

I have created also a StackBlitz example where you can see all the code.

like image 79
AlesD Avatar answered Nov 15 '22 05:11

AlesD