Reputation: 9122
Basically I want to do this:
<input type="file" ng-model="variable_in_scope">
When I pick a file, variable_in_scope
should get assigned to the file object picked. Also, if the value of variable_in_scope
gets changed anywhere else in my page, it should update the text next to the "Choose File" button to indicate that the chosen file has changed.
With any other type of input, this would just work.
I don't need to do anything fancy like actually see the contents of the file. Ultimately, I want to post it, but I've found out you can do this by setting the file object you get into a FormData object, without actually reading the contents into Javascript-land.
I've found other questions about picking files with angular, but none had a two-way binding solution.
Upvotes: 3
Views: 6551
Reputation: 51
Helped for me: FileUploader
Hide the input element with display: none; then use a label to go on top of the input element, and a label you connect to your scope variable. See numerous posts on SO. Like this:
<label for="idFileUpload" class="custom-file-upload">
SelectFile
</label>
<input type="file" id="idFileUpload" nv-file-select uploader="uploader">
<label id="idSelectedFile">{{selectedFile}}</label>
<md-button id="idUploadBtn" md-no-ink class="md-primary settingsBtns" ng-click="uploader.uploadAll()" ng-disabled="!uploader.getNotUploadedItems().length">Upload</md-button>
CSS:
input[type="file"] {
display: none;
}
.custom-file-upload {
background-color: green;
color: white;
border: 1px solid #ccc;
display: inline-block;
padding: 6px 12px;
cursor: pointer;
}
In the events the FileUploader fires, you can fetch the value of the input element. HTH
Upvotes: 0
Reputation: 9122
Angular doesn't support binding to file-type inputs, but I cobbled together a solution using a number of other answers.
app.directive('filePicker', filePicker);
filePicker.$inject = ['$log', '$document'];
function filePicker($log,$document) {
var directive = {
restrict: 'A',
require: 'ngModel',
scope: {
ngModel: '='
},
link: _link
};
return directive;
function _link(scope, elem, attrs, ngModel) {
// check if valid input element
if( elem[0].nodeName.toLowerCase() !== 'input' ) {
$log.warn('filePicker:', 'The directive will work only for input element, actual element is a', elem[0].nodeName.toLowerCase());
return;
}
// check if valid input type file
if( attrs.type != 'file' ) {
$log.warn('filePicker:', 'Expected input type file, received instead:', attrs.type, 'on element:', elem);
return;
}
// listen for input change
elem.on('change', function(e) {
// get files
var files = elem[0].files;
// update model value
scope.$apply(function() {
attrs.multiple ? scope.ngModel = files : scope.ngModel = files[0];
});
});
scope.$watch('ngModel', function() {
if (!scope.ngModel)
elem[0].value = ""; // clears all files; there's no way to remove only some
});
}
}
This solution showed me how to use a directive to implement a custom binding to ng-model. It enables accessing the contents of the file, so if you need that functionality you can add it back to my solution.
However, it had some problems with its binding. It would correctly set the value of my variable_in_scope
, but if there were other things bound to the value of variable_in_scope
, they wouldn't update. The trick was to use isolate scope and $apply
. Then you don't need to mess with this $setViewValue
business. Just set it and forget it.
That got me as far as one-way-binding. If I set a value to variable_in_scope
, however, the file picker still showed that I had the original file selected. In my case all I really want to do is clear the selected file. I found out the Javascript magic to do this and set up a $watch
on the ngModel
to trigger it.
If you want to set the file to a different value programmatically, good luck to you, because FileList
is read-only. The magic trick lets you clear the FileList
, but you can't add anything back. Maybe you can create a new FileList
and assign it to .files
, but at a cursory glance I didn't see a way to do that.
Upvotes: 0
Reputation: 101652
My answer on another question provides a way to do this with ng-model
, but since that question is not specifically about two way binding (and my answer is fairly hard to find there), I'll reproduce it here:
app.directive('bindFile', [function () {
return {
require: "ngModel",
restrict: 'A',
link: function ($scope, el, attrs, ngModel) {
el.bind('change', function (event) {
ngModel.$setViewValue(event.target.files[0]);
$scope.$apply();
});
$scope.$watch(function () {
return ngModel.$viewValue;
}, function (value) {
if (!value) {
el.val("");
}
});
}
};
}]);
To use it, you simply need to add this to your angular module and include a bind-file
attribute on the file pickers where you want to use it.
Upvotes: 3