Pass image from FileReader to form input in Angular 6
Asked Answered
F

2

5

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

Freehanded answered 15/9, 2018 at 17:39 Comment(4)
@user184994 Sorry, you lost me. Set what to an empty string? The imageInput ? But, I want it to have the image as a value.Freehanded
Sorry, I hadn't read the code properly. Can you create a StackBlitz with your code that reproduces the issue please?Abatis
By the way I replaced this.imageUpload.controls.imageInput.reset(files[0] ); with this.imageUpload.controls.imageInput.setValue(files[0]); . setValue makes more sense here. I am trying to fix this and when I create a stackblitz I will let you know.Freehanded
@Abatis This is the stackblitz I dont have a clue why it has Template parse errors, but at least this is the whole code and that gives me the problem. Thanks againFreehanded
A
8

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

Abatis answered 15/9, 2018 at 19:15 Comment(6)
Hi again. Great catch, I would not think of it in a million years. But there is a problem. If I drop the image and fill a name (the 2 required inputs for the form), the Submit button is still not activated, as if the form is not valid, so I guess the input file does not have a value? If I click and pick an image, everything works fine. Any suggestions? ThanksFreehanded
I try to think if maybe there is another way to do this, instead of "switching" the file from the drop to the file input. Maybe when on drop fire the imageInput ? But this makes no sense because it will again open the folder explorer. Is there another default way to set the value of the file input?Freehanded
Check this stackblitz . Using input.value seems to work for both cases. Problem now is that the file input has two different kinds of values (check the console) . I guess I could fix that if I set as value the result of the read image, so I wrote the commented out code in the readfiles function. But this does not work, it gives an error about the event.target.result.Freehanded
Sorry, this stackblitzFreehanded
@Freehanded just remove anything from drop(e) function and keep it exactly like imageChange function and you will get exactly same results on consoleEmbay
Is it possible to upload file in folder?Neophyte
P
1

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

Pied answered 25/9, 2018 at 10:2 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.