angular ng-bind-html and directive within it

58,493

Solution 1

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

Solution 2

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.

Solution 3

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 :)

Solution 4

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.

Solution 5

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.

Share:
58,493
Amitava
Author by

Amitava

I'm Javascript developer with over 8 years professional experience building full stack web applications. My strength is in Node.Js backend and React, Redux ecosystem for UI. I have 8+ years experience in NodeJs &amp; I've been building React apps since last 4+ years.

Updated on April 26, 2020

Comments

  • Amitava
    Amitava about 4 years

    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?

  • spaffy
    spaffy over 9 years
    In line #2, ie. function(scope, element, attrs), where did you get from those three arguments, scope, element and attrs?
  • Ben
    Ben about 9 years
    @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.
  • Sanyam Jain
    Sanyam Jain about 9 years
    Can i have the vice versa for it?
  • foozhan
    foozhan about 9 years
    this is not work in response of ajax but accepted answer work
  • Phil Nicholas
    Phil Nicholas over 8 years
    Warning: The fiddle for this answer works, but the .directive() code in the code posted in the answer does not.
  • Phil Nicholas
    Phil Nicholas over 8 years
    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!
  • Lakatos Gyula
    Lakatos Gyula over 8 years
    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.
  • Jason
    Jason over 7 years
    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?
  • Burak Tokak
    Burak Tokak over 7 years
    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
  • Gabriel Andrei
    Gabriel Andrei almost 7 years
    this one worked for me. the chosen answer would trigger "Error: $rootScope:infdig Infinite $digest Loop"
  • Dan King
    Dan King over 6 years
    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.
  • Manoj Lasantha
    Manoj Lasantha about 2 years
    I tried this option. Works like a charm! I also use $sce.trustAsHtml before passing it to the directive. Thanks!