How do I make angular.js reevaluate / recompile inner html?
Solution 1
You have to $compile
your inner html like
.directive('autotranslate', function($interpolate, $compile) {
return function(scope, element, attr) {
var html = element.html();
debugger;
html = html.replace(/\[\[(\w+)\]\]/g, function(_, text) {
return '<span translate="' + text + '"></span>';
});
element.html(html);
$compile(element.contents())(scope); //<---- recompilation
}
})
Solution 2
Here's a more generic method I developed to solve this problem:
angular.module('kcd.directives').directive('kcdRecompile', function($compile, $parse) {
'use strict';
return {
scope: true, // required to be able to clear watchers safely
compile: function(el) {
var template = getElementAsHtml(el);
return function link(scope, $el, attrs) {
var stopWatching = scope.$parent.$watch(attrs.kcdRecompile, function(_new, _old) {
var useBoolean = attrs.hasOwnProperty('useBoolean');
if ((useBoolean && (!_new || _new === 'false')) || (!useBoolean && (!_new || _new === _old))) {
return;
}
// reset kcdRecompile to false if we're using a boolean
if (useBoolean) {
$parse(attrs.kcdRecompile).assign(scope.$parent, false);
}
// recompile
var newEl = $compile(template)(scope.$parent);
$el.replaceWith(newEl);
// Destroy old scope, reassign new scope.
stopWatching();
scope.$destroy();
});
};
}
};
function getElementAsHtml(el) {
return angular.element('<a></a>').append(el.clone()).html();
}
});
You use it like so:
HTML
<div kcd-recompile="recompile.things" use-boolean>
<div ng-repeat="thing in ::things">
<img ng-src="{{::thing.getImage()}}">
<span>{{::thing.name}}</span>
</div>
</div>
JavaScript
$scope.recompile = { things: false };
$scope.$on('things.changed', function() { // or some other notification mechanism that you need to recompile...
$scope.recompile.things = true;
});
Edit
If you're looking at this, I would seriously recommend looking at the website's version as that is likely to be more up to date.
Solution 3
This turned out to work even better than @Reza's solution
.directive('autotranslate', function() {
return {
compile: function(element, attrs) {
var html = element.html();
html = html.replace(/\[\[(\w+)\]\]/g, function(_, text) {
return '<span translate="' + text + '"></span>';
});
element.html(html);
}
};
})
Reza's code work when scope
is the scope for all of it child elements. However, if there's an ng-controller or something in one of the childnodes of this directive, the scope variables aren't found. However, with this solution ^, it just works!
kornfridge
Updated on August 22, 2020Comments
-
kornfridge almost 4 years
I'm making a directive that modifies it's inner html. Code so far:
.directive('autotranslate', function($interpolate) { return function(scope, element, attr) { var html = element.html(); debugger; html = html.replace(/\[\[(\w+)\]\]/g, function(_, text) { return '<span translate="' + text + '"></span>'; }); element.html(html); } })
It works, except that the inner html is not evaluated by angular. I want to trigger a revaluation of
element
's subtree. Is there a way to do that?Thanks :)
-
kornfridge over 10 yearsHere's what I'm using it for: stackoverflow.com/questions/21483857/…
-
kentcdodds almost 10 years@Adam Barthelson, haha, thanks :-) I actually updated this on my site recently. So I would recommend looking at the site's version.
-
swenedo almost 10 yearsCan this directive be used to trigger a recompliation in angular 1.2? Do I have to use one-time binding expressions from angular 1.3?
-
Krzysztof Kaczor almost 10 yearsCan someone explain me:
return angular.element('<a></a>').append(el.clone()).html();
-
kentcdodds almost 10 yearsYes,
.html()
returns the contents of the element. Because we also want the root node ofel
, we first insert it into a fake element and then get the contents of that. -
jerico over 9 yearsIMHO it's not correct to re-compile an already compiled element (see the problems reported in @kornfridge's solution below).
-
jerico over 9 yearsAccess to the $parent scope from within a directive breaks encapsulation. If you want to do that then you should require a parent directive (see the require-attribute of the Angular directive configuration object) to establish such a dependency in a supported way.
-
Tasnim Reza over 9 years@jerico Inside of
link
function must you have to compile again when you are manipulating yourDOM
unless you are usingcompile
function. You mentioned @kornfridge's solution, he usedcompile
function. According to documentation docs.angularjs.org/guide/… i don't think this is the proper use case to usecompile
function. -
Tasnim Reza over 9 years@kornifridge can you make it clear
Reza's code work when scope is the scope for all of it child elements
? and how it is better ? according to documentation docs.angularjs.org/guide/… the best practice of usecompile
function isAny operation which can be shared among the instance of directives should be moved to the compile function for performance reasons.
-
jerico over 9 years@Reza: IMO the compile function works but is not the best solution either. The OP's intent could probably best be met by manipulating markup on the server, through an interceptor or in the template cache before compilation. This makes for a highly performant and stable solution as it completely removes the need for Angular bindings in translation. See how Google's DoubleClick did it (starts around 42:34 in the video).
-
jerico over 9 years@Reza: The documentation you cite states that the compile function is to manipulate the structure of the markup while the link function is just to bind it to a scope. As the OP wants to manipulate the markup including the bindings, I'd say that is quite obviously a use case of the compile function (but see my other comment for a better solution in this special case).
-
sirrocco about 9 years@kentcdodds - sorry for the asking here ... but can you see anything wrong in the way I'm using the directive? codepen.io/anon/pen/EjWGKd?editors=101
-
kentcdodds about 9 yearsYou're specifying
xcd-use-boolean
. It should be:kcd-use-boolean
-
sirrocco about 9 yearsThanks, but that's not all - I created a question to stop polluting this: stackoverflow.com/q/30650619/5246
-
Scott Byers over 8 yearsAdded an up-vote here for the last line with $compile... using element.contents() instead of elements.html() was not obvious and got me hung up. Thanks!