Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Pass image from FileReader to form input in Angular 6

I try to create an UI where there is a form with a couple of text fields, a input type="file" and a div that you can drop images to upload with the rest of the form.

My goal / logic

use the same div to either drop an image or click on it and open the folder explorer like the input type="file" behaves. Enabling clicking makes sense in small screens where it is practically impossible to "drag and drop". And since there is already a input type="file" in the form there is no reason to take the image from the div and append it to the form etc etc. I try to take the image that is dropped in the div, set it as a value in the input type="file" and submit the form once. (if user clicked on the div , then the input type="file" already has a value , so again I am free to submit the form again).

Here is the code

  <div id="imageDrop" (click)='imageInput.click()' (drop)="drop($event)" (dragover)="allowDrop($event)" #imageDrop>
  </div> 
  <input type="file" formControlName="imageInput" required #imageInput id="imageInput" (change)='imageChange($event)' > <!-- use css to hide it -->

So, when imageDrop is clicked, actually call the imageChange via (click)='imageInput.click()'

This is the typescript in the component.

//imageUpload is the name of the reactive form
acceptedImageTypes = {'image/png': true,'image/jpeg': true,'image/gif': true};
@ViewChild('imageDrop') imageDrop; 

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

  drop(e) {
    e.preventDefault();  
     //clear in case we selected something before via click
    this.imageUpload.controls.imageInput.reset();  
    this.imageDrop.innerHTML="";    
    this.checkfiles(e.dataTransfer.files);
  }

  imageChange(event){    
    this.imageDrop.innerHTML="";   
    this.checkfiles(event.target.files);    
  }//imageChange

  checkfiles(files){      
    if (this.acceptedImageTypes[files[0].type] !== true){
      this.imageDrop.nativeElement.innerHTML="Not an image";          
      return;   
    }
    else if (files.length>1){
      this.imageDrop.nativeElement.innerHTML="Only one image/time";           
      return;   
    }    
    else { this.readfiles(files); }
  }//checkfiles

  readfiles(files){
    const reader = new FileReader();
    let image = new Image();
    reader.onload =  (event) =>{
      this.imageDrop.nativeElement.innerHTML="";                
      let fileReader = event.target as FileReader;
      image.src = fileReader.result;
      image.width = 150; 
      this.imageDrop.nativeElement.appendChild(image);      
    };
    reader.readAsDataURL(files[0]);    

    if (this.imageUpload.controls.imageInput.value==null) {
        //if its null then means that we dragging an img, so the previous image from the input file is deleted
        //now we got to put this image to the input file in order to submit the form
      this.imageUpload.controls.imageInput.reset(files[0] );            
    }    
  }//readfiles

  imageUploadSubmitted(){
    //when form submit, for now just check image value to check if its the right one
    console.log('IMAGE VALUE SUBMIT = =  ',this.imageUpload.controls.imageInput.value);
  }

Errors

When I try to drag/drop an image, I get this 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 that points to this HTML line <div id="imageDrop" (click)='imageInput.click()' (drop)="drop($event)" (dragover)="allowDrop($event)" #imageDrop> but I am sure it is related to the

if (this.imageUpload.controls.imageInput.value==null) {
  this.imageUpload.controls.imageInput.reset(files[0] );            
} 

part of the readfiles function.

Any ideas how to fix this, so the file input can get a value and be then free to submit the form?

Thanks

like image 594
codebot Avatar asked Sep 15 '18 17:09

codebot


2 Answers

Okay, the line giving you he error was this.imageUpload.controls.imageInput.setValue(files[0]);

The reason is, the browser will prevent you from programmatically setting the file that way due to security problems.

Instead, you can use the e.dataTransfer.files directly:

let input = this.imageUpload.controls.imageInput as any;
input.files = files;  

Here is a fork of your stackblitz

like image 95
user184994 Avatar answered Nov 15 '22 03:11

user184994


What you are trying to do can't be achieve this way (cause of what every people said, security reason etc...).

To acheive what you want to do, an Angular Solution is to create a custom component working with [(ngModel)] formControlName by implementing the ControlValueAccessor.

By doing this you will be able to put what ever you want as value into the FormControl.

Implementing ControlValueAccessor is part of many tutorial : https://blog.angularindepth.com/never-again-be-confused-when-implementing-controlvalueaccessor-in-angular-forms-93b9eee9ee83

Here you can find some code of my own, an image-chooser working in a Form :

https://github.com/xrobert35/asi-ngtools/blob/master/src/components/asi-image-chooser/asi-image-chooser.component.ts

example : https://ng-tools.asi.fr/views/showroom/asi-ngtools/components/asi-image-chooser

like image 21
xrobert35 Avatar answered Nov 15 '22 04:11

xrobert35