Reputation: 1221
I’m new to Angular, and working with an app that has some existing JavaScript code. Inside a loop, the app appends file input buttons that are added to table rows in the view like this:
cellSeq.innerHTML = cellSeq.innerHTML +
'<input type="file" name="f" multiple="multiple" file-bind="" id="my-files-input-' +
counter + '">';
So, the buttons are made unique on each table row by adding the counter
variable to their IDs. I have a file-reading directive, called file-bind
, which I can attach to a single button (without the counter
), which processes the files that are read by the input.
In a test this works on a single button, which I just stick into the HTML manually (without the counter variable on its ID):
<input type="file" name="f" multiple="multiple" file-bind="" />
Here's the file-reading directive signature (not too exciting):
angular.module('myApp').directive('fileBind', function() {
'use strict';
return function(scope, element, attrs) {
element.bind('change', function(evt) {
// ...read files
I suspect the issue is in the way I create the directive. What I want to do is bind my file-bind directive to each of these unique buttons, and update the respective section of the view once the files are read.
How do I add a unique directive (in terms of the execution context) to each of the buttons?
Edit It appears that I need a compile
function, as the repeated element is created in pure JavaScript. Where do I place the compile function, given that my directive returns a function (rather than an object, where I could put 'compile: function()...')?
Upvotes: 1
Views: 206
Reputation: 33833
When adding new html to the DOM programatically with jQuery as opposed to adding it with angular (via ng-repeat, ng-if, etc), Angular is blissfully unaware that anything has changed and that it needs to respond accordingly.
As a result, you need to call $compile on the newly added HTML to alert Angular that it needs to re-parse the template and properly compile and link any found directives.
In order to do this, you need to pass both the element to compile and the element's scope to $compile()
.
cellSeq.innerHTML = cellSeq.innerHTML +
'<input type="file" name="f" multiple="multiple" file-bind="" id="my-files-input-' +
counter + '">';
var element = angular.element(cellSeq);
var scope = element.scope();
$compile(element.contents())(scope);
The above assumes that you are using jQuery in a context where you can inject $compile
(an angular controller). However, this is most likely not your use case. If you are adding the html via jQuery in an externally managed component, such as a plugin, you have to get more creative since you have to retrieve the $injector
instance so that you can get access to $compile
.
var outerEl = document.getElementById('outer');
outerEl.innerHTML = counter + '<input type="file" name="f"
multiple="multiple" file-bind="" id="my-files-input-' +
counter + '">';
var element = angular.element(outerEl);
var scope = element.scope();
// retrieve the default injector instance
var $injector = angular.injector(['ng']);
$injector.invoke(["$compile", "$rootScope", function($compile, $rootScope) {
var $scope = scope || $rootScope;
$compile(element.contents())($scope);
$scope.$digest();
}]);
You can see this working this plunkr
If your element does not contain scope, you will want to bubble up to the $rootScope
, since you must have a scope for $compile
to run.
We need to call $scope.$digest()
after compilation in this scenario since we are calling $compile
from jQuery, outside of the normal digest cycle. As a result, if $digest
is not called, the model bindings will not be properly refreshed.
As you can see, this is a LOT of complication just to add some HTML! The best approach is to always think in an "angulary" way whenever possible and to use the appropriate angular tools, directives, for managing DOM manipulation.
Upvotes: 1