Closed alexandis closed 2 months ago
Hi @alexandis,
I want to confirm I understand your problem correctly.
You want to access the files source paths before upload. I assume you want to use the webkitRelativePath
property.
What exactly are you checking in both your handlers, onDropDocuments
and onSelectDocuments
?
Hi.
I use my custom logic in onDropDocuments
method to handle a drag-n-drop scenario. This logic creates the collection of files to-be-uploaded, preserving the original file path using FileSystemEntry
=> fullPath
property (otherwise the original file name does not contain this information). Here, I have to copy the original file to a new file to change its name, because File
is immutable (this.files.push(this.copyFile(file, fileEntry.fullPath.replace(/^\/[A-Z]_drive/, '')));
)
And I use onSelectDocuments
to handle an ordinary logic - selecting files folder with standard system "Open" button - in this case the original file path is available straight in File
=> name
. In general, I use onSelectDocuments
to build a common logic of displaying the files to-be-uploaded (the folder structure with the total amount of files and its volume per each subfolder), no matter if it's a drag-n-drop or a system select.
And I am getting issues related to the fact that for drag-n-drop both methods are called. So I would appreciate your proposals how to simplify my logic to still achieve the goal.
The team member broke the page availability and I cannot test my page right away. Are you implying that I can use webkitRelativePath
next to webkitdirectory
and it will resolve my issue and besides I will be able to through away some of my code?
Please have a look at PR #80. This might fix your problem.
I first decided to ignore the relative path info, but I thinks it's time to add it back in. The only way to achieve a consistent behavior is to add a new property.
Please review the PR code. You might want to checkout the branch and try it locally. If this fits your needs, we may release a new version.
Thank you. I will check and let you know.
Another small question: if i am going to make the list of file replenishable (i.e. i can delete the selected files from the list, make selection again from other location merging with the previous selection, i.e. I am going to store the list of all files internally) - then what is the best way of using this component in terms of memory efficiency? <input type="file" fileInput (change)="onSelectDocuments($event)" [multiple]="true" />
, so no model or control to store the selection or I still have to use formControl
/ ngModel
and the relevant events?
UPDATE: I've cloned your repo and took PR branch, but I am not sure what to do next. We use Angular v15.2 in all the projects, you use version 18, so I don't know where my attempts will get me after all... :) Maybe I should have used another approach - just patch the existing code, not trying to install a patched version of the component?
After cloning the repo and npm install
follow these steps to run a test app.
Then upload some files and check if the relativePath
property is set. You should be able to keep the file paths on drop AND when using the file picker.
Migrating to Angular 18 is recommended anyway, as a workaround you could install the latest version with --force
flag in your project.
About your other question, the internal state is stored in the value
property.
I would recommend to use a FormControl
for the most flexible state management.
Use the property mode
(see config) to change the behavior. By default, a second file selection will overwrite the first one. But you may also "stack" all selected files.
Hi Paul. Thank you, I've installed PR version to my project. Seems like it does not work fully correct or I am missing something. Here is my usage (for "folder selection" scenario):
<ngx-mat-dropzone>
<input type="file" fileInput [(ngModel)]="files" [ngModelOptions]="{ standalone: true }" [multiple]="true" relativePath mode="append" webkitdirectory (ngModelChange)="buildFolderOutput()" />
</ngx-mat-dropzone>
When I select the folder in a system way, via button click - everything looks as expected: webkitRelativePath
property is filled with the correct subfolder path.
However, when I drag-n-drop it - webkitRelativePath
property of its files is empty.
Another issue: I have the button to remove the selected files, which does this: this.files = [];
However, when I select the files again, it pulls the previously selected files from somewhere and merges them with the ones currently selected...
And probably the last question here: how to avoid adding duplicates? It might look like a user responsibility, but maybe the component should offer some fool-proof mechanisms?
Hey, thanks for the feedback.
You're almost there. On drop, the webkitRelativePath
property will be empty, since I can't overwrite it with the full path value because it's readonly. Therefore, I added the new relativePath
property which should have the expected value.
As for your other bug of clearing the array, I will have to look into this in the next few days. Maybe use a FormControl
as a workaround?
When using mode="replace"
(which is the default) on the <input type="file">
element, no duplicates should be possible because the files array will be reset on each change. Any other validation will be left to you as the consumer. You might check for duplicate file names in the FormControl
valueChanges
handler.
Maybe I am missing something obvious, but on drag-n-drop console.log((file as any).relativePath)
returns me undefined
inside buildFolderOutput
ngModelChange
handler when I loop through files
array... Probably it was taken care only for formControl
scenario? I think it should be consistent for both scenarios, because someone will always prefer one over another.
Maybe use a FormControl as a workaround
I might check it out later on, but if it was possible to make it work for ngModel
, I would be happy ;)
Okay, so let's try to figure this out. Please setup the local playground dev app as described in my previous post. With this minimum example I got it working as expected.
import { Component } from '@angular/core';
import { File } from '@ngx-dropzone/cdk';
@Component({
selector: 'app-root',
template: `<div class="app-container">
<mat-form-field appearance="fill">
<mat-label>Drop anything!</mat-label>
<ngx-mat-dropzone>
<input
type="file"
fileInput
multiple
webkitdirectory
[(ngModel)]="files"
[ngModelOptions]="{ standalone: true }"
(ngModelChange)="buildFolderOutput()"
/>
</ngx-mat-dropzone>
</mat-form-field>
</div>`,
})
export class AppComponent {
files: File[] = [];
buildFolderOutput() {
this.files.forEach((f) => console.log(f.relativePath));
}
}
To be able to reset the internal value, even with mode="append"
, I expanded the PR to add a new clear()
method to the directive. Use it like so.
<input type="file" fileInput #fi="fileInput" mode="append" />
<button (click)="fi.clear()">Reset</button>
Thank you, i will try this on Monday. Quick question: is it possible to delete a single item from files array? I should have asked that in the first place, not deleting all the elements, sorry.
Yes, if you have set mode="replace"
you can just update your files array.
this.files = this.files.filter(f => f.name !== "test");
Yes, if you have set
mode="replace"
you can just update your files array.this.files = this.files.filter(f => f.name !== "test");
Well, I prefer this.files.splice(index, 1)
- I think it might be faster... But anyway, the deletion of one item seems to be OK.
For the deletion of all files i've tried to use clear()
method of the directive... But it does not always work. The matter is that the moment of clearing the model and creating the dragzone component are separated in time. So I have to use @ViewChild
to access the directive. Though, sometimes it give me "TypeError: Cannot read properties of undefined (reading 'clear')" error.
I've tried to use this.files = []
instead, which supposedly has to work identically? But nevertheless I believe I saw some cases, when the previous "state" has not been cleared and I saw the previously selected files after clearing the list this way...
Thanks for your feedback again.
I looked into this again and as it seems like I introduced some inconsistencies. I updated the implementation again and removed the clear()
method. Please pull the latest branch changes and try
this.files = [];
again. It should now work as expected.
Looks fine now, thank you.
UPDATE: on future check, I've observed yet another thing :)
When I select a folder targeting a whole USB flashdrive - both webkitRelativePath
and relativePath
look identical and predictable, i.e. let's say D:\info.txt
.
However, when I drag-n-drop a whole USB flashdrive - webkitRelativePath
is empty (which is probably expected, since I here need to rely on relativePath
), but relativePath
itself contains a long weird prefix text, like "D_drive/System Volume Information/info.txt
... Is it possible to uniform it - so the path always look in a predictable way, no matter if it's a local folder, external drive or network mapped path...
I want to interfere with the default Browser behavior as less as possible. Therefore, I will leave the handling of this edge case to the consumer (n this case you), depending on the use case.
I will now merge the PR and release a new version that you might want to pull into your project.
Hi, @hackingharold.
I occasionally found out, that relativePath
does not work anymore as it worked before - at least, on the latest version of Chrome.
Here is the source file:
Please have a look - this is what is looks like when I drag-n-drop a file:
And here is what it looks like when I use a standard "open" way: .
If I use webkitdirectory
attribute on other page, where I need to select a whole folder - it does not look right either, you can check it out yourself...
Hey @alexandis,
I just tested this with ngx-dropzone@18.1.0
in Chrome 126 using my StackBlitz example and it's still working as expected.
Note that the webkitdirectory
attribute is required to be able to get the relative path when using the native file picker.
Please provide some more information if you cannot get it to work.
Hi. I was able to repro the issue on your example with some modification based on my case.
Interesting fact is that it USED TO work some time ago - that's why I thought it might be due to the change in the browser's security or something like that :)
Also the question: if I need to use webkitdirectory
always - how to make native file picker give me a file path, if i use it for a file selection, not for a folder selection?
Browsers Chrome Version 126.0.6478.183 (Official Build) (64-bit)
I am confused now, your example is working as expected for me. Please see the following table and explain what you expect differently:
webkitdirectory ❌ |
webkitdirectory ✅ |
|
---|---|---|
selecting file(s) from file picker | relativePath is empty because no directory was selected |
not possible, forbidden by Browser |
selecting folder from file picker | not possible, forbidden by Browser | relativePath is set based on selected folder |
dropping file(s) | relativePath equals the name because no directory was selected |
relativePath equals the name because no directory was selected |
dropping folder | relativePath is set based on dropped folder |
relativePath is set based on dropped folder |
For the file picker, you have to decide to either allow folders or single files by setting the webkitdirectory
attribute. This is native Browser behavior I cannot do anything about.
Thank you, I understand now... A directory selection mode works well, you are right - my bad (indeed - it's RELATIVE path, not ABSOLUTE :)).
Regarding a file selection mode, which now I am coping with... So, to your knowledge, there's no any way to find out an absolute source path of the file to be uploaded via javascript?
No, reading the absolute path of a user-selected file is not possible for security reasons. You might want to read into the Browser's File System API, but this is out of scope of this library.
Thank you so much.
Hi, I am back :) I've used the following markup to cover both drag-n-drop and usual selection for multiple file upload:
Unfortunately, it works well only for an ordinary select, because only
onSelectDocuments
is triggered. When I use drag-n-drop, bothonSelectDocuments
andonDropDocuments
handlers are triggered and as a result, I get the issue, because the code has to process the same logic twice. How to avoid this and clearly divide the handlers?I used some custom logic, that's why I use
onDropDocuments
handler. And also I found using ngModel in my case instead of formControl to be preferrable.In fact, the main reason for having the custom code was to get the initial source path of the file by all means, since we needed to pass this info to server. If I could obtain this information in other way, probably I would get rid of the issue as well by eliminating some custom handlers...