I'm trying to create a directive with angularjs but I found a problem.
Here is the js code of the directive
angular.module('xxxFileUploader', [])
.directive('xxxFileUploader', function() {
return {
restrict: 'E',
templateUrl: 'fileUploader.html',
scope: {
onChange: '='
},
link: function(scope, element, attrs) {
element.find('input[type="file"]').change(function() {
var input = this;
if (input.files && input.files[0]) {
scope.file = input.files[0];
scope.onChange(input.files[0]);
});
}
};
});
the directive template
<span style="position:relative;">
<a class='btn btn-primary' href='javascript:;'>
Choose File...
</a>
<input type="file"
name="{{field.name}}"
style='position:absolute;z-index:2;top:0;left:0;filter: alpha(opacity=0);opacity:0;background-color:transparent;color:transparent;'
/>
<span class='label label-info'>{{file.name}}</span>
</span>
a piece of html file where I use the directive
<xxx-file-uploader on-change="loadFile(field.name)" ></xxx-file-uploader>
and the relevant piece of my controller
$scope.loadFile = function(fieldName) {
return function(file) {
// do things with both fieldName and file...
};
};
This code should just customize the look of an input of type file and execute a callback when a file is uploaded.
My problem comes from the fact that at the moment I need to use a change callback that is built dynamically for every fileUploader. As you can see the callback is built with a parameter fieldName that is known at link-time and a parameter file that is known at "execution-time".
With this code I get the AngularJS “Error: 10 $digest() iterations reached. Aborting!” because (I think) the function is generated again and again and the digests don't match.
A poor-man solution would be to pass the fieldName as another scope variable. Another again would be maybe to pass the function as a string (@) and build the callback when I need it.
Any suggestion on how to make this directive work without changing its interface? I'm quite new to angularjs directives (this is my first one!) so if you see anything wrong in the code please point it out.
Thank you.
First edit:
I tried to change the scope of the directive as I've been suggested from
scope: {
onChange: '='
},
to
scope: {
onChange: '&'
},
and I changed the callback call from
scope.onChange(input.files[0]);
to
scope.onChange()(input.files[0]);
now it works but I'm not completely satisfied with the result.
It looks like a need to call the onChange function to get my "real" callback. Is there a way to have it executed implicitly in this case?
What I mean is that if I write
<xxx-file-uploader on-change="loadFile(field.name)" ></xxx-file-uploader>
it should understand to execute the function
if instead i write
<xxx-file-uploader on-change="doSomething" ></xxx-file-uploader>
it should recognize that doSomething is the "real" callback I want it to execute (with the file parameter).
The way you are using onChange: '='
and the function is incorrect. The following solution DOES change the interface of the directive a bit, but I think renaming onChange
to onChangeFactory
reflects better on its intended usage. The name can remain onChange
however if you do not mind the small inconsistency.
Pass loadFile()
itself as:
<xxx-file-uploader on-change-factory="loadFile(fname)" ></xxx-file-uploader>
Modify the directive to use the function binding:
scope: {
onChangeFactory: "&"
}
Call the factory and get the actual callback in link()
:
link: function(scope, element, attrs) {
// HOW ARE YOU GETTING FIELDNAME???
var onChange = scope.onChangeFactory({fname: FIELDNAME});
element.find('input[type="file"]').change(function() {
var input = this;
if (input.files && input.files[0]) {
scope.file = input.files[0];
onChange(input.files[0]); // <--- NOTE CHANGE HERE TOO
}
});
}
Do pay attention on the way of calling onChangeFactory()
from inside the directive! I guess there will be some gaps (e.g. the filename), but I believe this is a good starting point.
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