angular ng-bind-html and directive within it
Asked Answered
T

6

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?

Treasatreason answered 2/7, 2013 at 4:48 Comment(4)
Look at my answer here #17344196Brittaney
@Brittaney 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!Treasatreason
@SamSerious are you able to show how you did what you did for the filters?Kenward
the above solutions don't handle multiple changes of the value a better solution https://mcmap.net/q/219178/-render-angularjs-directive-on-http-loaded-contentHemorrhage
R
188

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

Rintoul answered 2/7, 2013 at 13:14 Comment(5)
In line #2, ie. function(scope, element, attrs), where did you get from those three arguments, scope, element and attrs?Publius
@Publius - 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.Rundown
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!Dollop
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?Outlandish
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 @apoplexyVanatta
L
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.

Latchstring answered 6/12, 2013 at 17:57 Comment(5)
Can i have the vice versa for it?Dey
this is not work in response of ajax but accepted answer workDonofrio
Warning: The fiddle for this answer works, but the .directive() code in the code posted in the answer does not.Dollop
this one worked for me. the chosen answer would trigger "Error: $rootScope:infdig Infinite $digest Loop"Jessicajessie
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.Pleurodynia
S
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 :)

Separable answered 7/8, 2015 at 14:28 Comment(2)
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.Tobar
I tried this option. Works like a charm! I also use $sce.trustAsHtml before passing it to the directive. Thanks!Rachmaninoff
D
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.

Despain answered 23/7, 2014 at 12:4 Comment(0)
M
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.

Mckinnon answered 8/1, 2015 at 15:22 Comment(0)
H
-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>
Happiness answered 20/3, 2014 at 13:37 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.