96

Plunker Link

I have a element which I would like to bind html to it.

<div ng-bind-html="details" upper></div>

That works. Now, along with it I also have a directive which is bound to the bound html:

$scope.details = 'Success! <a href="#/details/12" upper>details</a>'

But the directive upper with the div and anchor do not evaluate. How do I make it work?

4
  • 3
    Look at my answer here stackoverflow.com/questions/17343696/… Commented Jul 2, 2013 at 6:17
  • @Chandermani not exactly using directive inside ng-bind-html-unsafe but using filter. But it will do, I just created a filter and passed to the directive. Thanks! Commented Jul 3, 2013 at 4:27
  • @SamSerious are you able to show how you did what you did for the filters? Commented Sep 26, 2013 at 21:56
  • the above solutions don't handle multiple changes of the value a better solution stackoverflow.com/a/25516311/3343425 Commented Aug 26, 2014 at 22:50

6 Answers 6

190

I was also facing this problem and after hours searching the internet I read @Chandermani's comment, which proved to be the solution. You need to call a 'compile' directive with this pattern:

HTML:

<div compile="details"></div> 

JS:

.directive('compile', ['$compile', function ($compile) { return function(scope, element, attrs) { scope.$watch( function(scope) { // watch the 'compile' expression for changes return scope.$eval(attrs.compile); }, function(value) { // when the 'compile' expression changes // assign it into the current DOM element.html(value); // compile the new DOM and link it to the current // scope. // NOTE: we only compile .childNodes so that // we don't get into infinite loop compiling ourselves $compile(element.contents())(scope); } ); }; }]) 

You can see a working fiddle of it here

Sign up to request clarification or add additional context in comments.

5 Comments

In line #2, ie. function(scope, element, attrs), where did you get from those three arguments, scope, element and attrs?
@spaffy - they're part of Angular framework's signature for the link property. They'll be passed automatically each time when link is called by the Angular framework. They'll always be available.
Well done. You saved me those same hours of searching. I'm pulling content from SharePoint view REST API, which itself contains Angular markup such as ng-repeat. Your directive made it all work. Thanks!
Thanks for your directive it fixed the problems I was having. Now the angular code gets compiled but too many times. A ng-repeat with 3 object turns into the same values just 3x each. Whats going wrong here?
If you have been using $sce.trustAsHtml from another function to create the HTML that will be "compiled" with this directive, you should remove it. Thanks to @apoplexy
36

Thanks for the great answer vkammerer. One optimization I would recommend is un-watching after the compilation runs once. The $eval within the watch expression could have performance implications.

 angular.module('vkApp') .directive('compile', ['$compile', function ($compile) { return function(scope, element, attrs) { var ensureCompileRunsOnce = scope.$watch( function(scope) { // watch the 'compile' expression for changes return scope.$eval(attrs.compile); }, function(value) { // when the 'compile' expression changes // assign it into the current DOM element.html(value); // compile the new DOM and link it to the current // scope. // NOTE: we only compile .childNodes so that // we don't get into infinite loop compiling ourselves $compile(element.contents())(scope); // Use un-watch feature to ensure compilation happens only once. ensureCompileRunsOnce(); } ); }; }]); 

Here's a forked and updated fiddle.

5 Comments

Can i have the vice versa for it?
this is not work in response of ajax but accepted answer work
Warning: The fiddle for this answer works, but the .directive() code in the code posted in the answer does not.
this one worked for me. the chosen answer would trigger "Error: $rootScope:infdig Infinite $digest Loop"
You shouldn't need the explict $eval - you can just use attrs.compile directly in place of the watched anonymous function. If you just provide a string expression, angular will call $eval on it anyway.
28

Add this directive angular-bind-html-compile

.directive('bindHtmlCompile', ['$compile', function ($compile) { return { restrict: 'A', link: function (scope, element, attrs) { scope.$watch(function () { return scope.$eval(attrs.bindHtmlCompile); }, function (value) { // Incase value is a TrustedValueHolderType, sometimes it // needs to be explicitly called into a string in order to // get the HTML string. element.html(value && value.toString()); // If scope is provided use it, otherwise use parent scope var compileScope = scope; if (attrs.bindHtmlScope) { compileScope = scope.$eval(attrs.bindHtmlScope); } $compile(element.contents())(compileScope); }); } }; }]); 

Use it like this :

<div bind-html-compile="data.content"></div> 

Really easy :)

2 Comments

Be careful, if you pass something like this: "$scope.loadContent = function() { return $sce.trustAsHtml(require('html/main-content.html')); };" to it you can get infinite digest loop. Without the trustAsHtml it works.
I tried this option. Works like a charm! I also use $sce.trustAsHtml before passing it to the directive. Thanks!
13

Unfortunately I don't have enough reputation to comment.

I could not get this to work for ages. I modified my ng-bind-html code to use this custom directive, but I failed to remove the $scope.html = $sce.trustAsHtml($scope.html) that was required for ng-bind-html to work. As soon as I removed this, the compile function started to work.

Comments

6

For anyone dealing with content that has already been run through $sce.trustAsHtml here is what I had to do differently

function(scope, element, attrs) { var ensureCompileRunsOnce = scope.$watch(function(scope) { return $sce.parseAsHtml(attrs.compile)(scope); }, function(value) { // when the parsed expression changes assign it into the current DOM element.html(value); // compile the new DOM and link it to the current scope. $compile(element.contents())(scope); // Use un-watch feature to ensure compilation happens only once. ensureCompileRunsOnce(); }); } 

This is only the link portion of the directive as I'm using a different layout. You will need to inject the $sce service as well as $compile.

Comments

-2

Best solution what I've found! I copied it and it work's exactly as I needed. Thanks, thanks, thanks ...

in directive link function I have

app.directive('element',function($compile){ . . var addXml = function(){ var el = $compile('<xml-definitions definitions="definitions" />')($scope); $scope.renderingElement = el.html(); } . . 

and in directive template:

<span compile="renderingElement"></span> 

Comments

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.