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
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
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
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With