By giraffe_sense


2013-08-09 23:55:34 8 Comments

The Situation

Nested within our Angular app is a directive called Page, backed by a controller, which contains a div with an ng-bind-html-unsafe attribute. This is assigned to a $scope var called 'pageContent'. This var gets assigned dynamically generated HTML from a database. When the user flips to the next page, a called to the DB is made, and the pageContent var is set to this new HTML, which gets rendered onscreen through ng-bind-html-unsafe. Here's the code:

Page directive

angular.module('myApp.directives')
    .directive('myPage', function ($compile) {

        return {
            templateUrl: 'page.html',
            restrict: 'E',
            compile: function compile(element, attrs, transclude) {
                // does nothing currently
                return {
                    pre: function preLink(scope, element, attrs, controller) {
                        // does nothing currently
                    },
                    post: function postLink(scope, element, attrs, controller) {
                        // does nothing currently
                    }
                }
            }
        };
    });

Page directive's template ("page.html" from the templateUrl property above)

<div ng-controller="PageCtrl" >
   ...
   <!-- dynamic page content written into the div below -->
   <div ng-bind-html-unsafe="pageContent" >
   ...
</div>

Page controller

angular.module('myApp')
  .controller('PageCtrl', function ($scope) {

        $scope.pageContent = '';

        $scope.$on( "receivedPageContent", function(event, args) {
            console.log( 'new page content received after DB call' );
            $scope.pageContent = args.htmlStrFromDB;
        });

});

That works. We see the page's HTML from the DB rendered nicely in the browser. When the user flips to the next page, we see the next page's content, and so on. So far so good.

The Problem

The problem here is that we want to have interactive content inside of a page's content. For instance, the HTML may contain a thumbnail image where, when the user clicks on it, Angular should do something awesome, such as displaying a pop-up modal window. I've placed Angular method calls (ng-click) in the HTML strings in our database, but of course Angular isn't going to recognize either method calls or directives unless it somehow parses the HTML string, recognizes them and compiles them.

In our DB

Content for Page 1:

<p>Here's a cool pic of a lion. <img src="lion.png" ng-click="doSomethingAwesone('lion', 'showImage')" > Click on him to see a large image.</p>

Content for Page 2:

<p>Here's a snake. <img src="snake.png" ng-click="doSomethingAwesone('snake', 'playSound')" >Click to make him hiss.</p>

Back in the Page controller, we then add the corresponding $scope function:

Page controller

$scope.doSomethingAwesome = function( id, action ) {
    console.log( "Going to do " + action + " with "+ id );
}

I can't figure out how to call that 'doSomethingAwesome' method from within the HTML string from the DB. I realize Angular has to parse the HTML string somehow, but how? I've read vague mumblings about the $compile service, and copied and pasted some examples, but nothing works. Also, most examples show dynamic content only getting set during the linking phase of the directive. We would want Page to stay alive throughout the life of the app. It constantly receives, compiles and displays new content as the user flips through pages.

In an abstract sense, I guess you could say we are trying to dynamically nest chunks of Angular within an Angular app, and need to be able to swap them in and out.

I've read various bits of Angular documentation multiple times, as well as all sorts of blog posts, and JS Fiddled with people's code. I don't know whether I'm completely misunderstanding Angular, or just missing something simple, or maybe I'm slow. In any case, I could use some advice.

5 comments

@Ramesh M 2016-11-18 08:13:47

Try this below code for binding html through attr

.directive('dynamic', function ($compile) {
    return {
      restrict: 'A',
      replace: true,
      scope: { dynamic: '=dynamic'},
      link: function postLink(scope, element, attrs) {
        scope.$watch( 'attrs.dynamic' , function(html){
          element.html(scope.dynamic);
          $compile(element.contents())(scope);
        });
      }
    };
  });

Try this element.html(scope.dynamic); than element.html(attr.dynamic);

@Rupesh Kumar Tiwari 2015-09-14 17:59:15

You can use

ng-bind-html https://docs.angularjs.org/api/ng/service/$sce

directive to bind html dynamically. However you have to get the data via $sce service.

Please see the live demo at http://plnkr.co/edit/k4s3Bx

var app = angular.module('plunker', []);
app.controller('MainCtrl', function($scope,$sce) {
    $scope.getHtml=function(){
   return $sce.trustAsHtml("<b>Hi Rupesh hi <u>dfdfdfdf</u>!</b>sdafsdfsdf<button>dfdfasdf</button>");
   }
});

  <body ng-controller="MainCtrl">
<span ng-bind-html="getHtml()"></span>
  </body>

@jaggedsoft 2017-02-12 01:10:01

Thanks! This helped me. However, you need to include ngSanitize and angular-sanitize.js: var myApp = angular.module('myApp', ['ngSanitize']);

@cyan 2017-04-12 07:53:59

that worked for me too during binding bootstrap icon to material md-list span element

@kwerle 2014-10-15 19:04:28

Found in a google discussion group. Works for me.

var $injector = angular.injector(['ng', 'myApp']);
$injector.invoke(function($rootScope, $compile) {
  $compile(element)($rootScope);
});

@Alexandros Spyropoulos 2014-01-30 00:25:22

In angular 1.2.10 the line scope.$watch(attrs.dynamic, function(html) { was returning an invalid character error because it was trying to watch the value of attrs.dynamic which was html text.

I fixed that by fetching the attribute from the scope property

 scope: { dynamic: '=dynamic'}, 

My example

angular.module('app')
  .directive('dynamic', function ($compile) {
    return {
      restrict: 'A',
      replace: true,
      scope: { dynamic: '=dynamic'},
      link: function postLink(scope, element, attrs) {
        scope.$watch( 'dynamic' , function(html){
          element.html(html);
          $compile(element.contents())(scope);
        });
      }
    };
  });

@DzeryCZ 2014-02-27 23:57:31

Hello, If I use element.html it return me TypeError: Cannot call method 'insertBefore' of null. So after some googling about that I find that I must use element.append But If I use that directive on multiple places - it generate multiplicate HTML. So 2 directives generate 4 same HTML code. Thanks for your answer.

@Alexandros Spyropoulos 2014-03-06 15:34:10

I wouldn't use append in your place, I will have a look on that tonight and I'll get back to you. To be honest, I used this directive in quite a few places in a page without any issue. I'll try to reproduce the problem and I'll get back to you.

@Buu Nguyen 2014-03-31 18:46:54

@AlexandrosSpyropoulos I just test and see that my code runs okay even with 1.2.12. I think you probably missed the declaration <div dynamic="html"> in the HTML? (With that declaration, $watch watches the 'html' property in scope, not the actual HTML as you mentioned, so there should be no invalid char error.) If not, send me the plunkr that shows it doesn't work, I'll see what's wrong.

@Alexandros Spyropoulos 2014-04-04 00:17:22

You're probably right. I've been expecting back then, that html is actually a variable that contains html :P. It's good idea though to set a scope on your Directives. umur.io/…

@Mital Pritmani 2014-04-10 12:40:12

$compile(ele.contents())(scope); - this line solved my issue of not compiling angular components which are added dynamically. Thanks.

@Buu Nguyen 2013-08-10 01:55:42

ng-bind-html-unsafe only renders the content as HTML. It doesn't bind Angular scope to the resulted DOM. You have to use $compile service for that purpose. I created this plunker to demonstrate how to use $compile to create a directive rendering dynamic HTML entered by users and binding to the controller's scope. The source is posted below.

demo.html

<!DOCTYPE html>
<html ng-app="app">

  <head>
    <script data-require="[email protected]" data-semver="1.0.7" src="https://ajax.googleapis.com/ajax/libs/angularjs/1.0.7/angular.js"></script>
    <script src="script.js"></script>
  </head>

  <body>
    <h1>Compile dynamic HTML</h1>
    <div ng-controller="MyController">
      <textarea ng-model="html"></textarea>
      <div dynamic="html"></div>
    </div>
  </body>

</html>

script.js

var app = angular.module('app', []);

app.directive('dynamic', function ($compile) {
  return {
    restrict: 'A',
    replace: true,
    link: function (scope, ele, attrs) {
      scope.$watch(attrs.dynamic, function(html) {
        ele.html(html);
        $compile(ele.contents())(scope);
      });
    }
  };
});

function MyController($scope) {
  $scope.click = function(arg) {
    alert('Clicked ' + arg);
  }
  $scope.html = '<a ng-click="click(1)" href="#">Click me</a>';
}

@giraffe_sense 2013-08-12 18:29:34

Thanks so much, Buu! Creating the attribute directive and adding the scope watch function were the two things I was missing. Now that this is working, guess I'll read up again on directives and $compile, to better understand what's going on under the hood.

@Craig Morgan 2013-11-19 08:46:44

Me too!The Angular team could really do with improving the docs on this.

@Mital Pritmani 2014-04-10 12:39:23

$compile(ele.contents())(scope); - this line solved my issue of not compiling angular components which are added dynamically. Thanks.

@anam 2014-05-07 10:45:10

@BuuNguyen inside teplateURL suppose if u include some dynamic htmnl page using ng-bind-html , then using compile doesnt work gives error from some unsafe content other side using trustAsHTml only remove unsafe error doesnt compile , any suggestions?

@Buu Nguyen 2014-05-07 20:13:36

@simmisimmi it's how ng-bind-html works. Either include $sanitize or use trustAsHtml correctly. See this docs.angularjs.org/api/ng/directive/ngBindHtml

@AnxiousdeV 2014-06-10 22:54:59

How would you dynamically add "<div dynamic="html"></div>" In my case I need to add table rows that contain ng-click directives so the dynamic directive itself needs to be injected

@tonejac 2015-05-29 07:00:48

So if there are ng-click attributes in the child elements of the container with the dynamic='html' attribute will those also be compiled or do you have to do a separate directive for each element that needs compiling?

@landed 2015-06-04 09:59:22

I like this example but it doesnt get mine working. I have a switch statement that happens due to user choice so its dynamic. Depending on that I want to insert html containing directive. The directive works if I place it in the natural bootstrap phase. However I have this that is simply not firing --- case 'info': $scope.htmlString = $sce.trustAsHtml('<div dynamic="htmlString">dddzzz</div>'); break; --- when I want to do something like --- $compile($sce.trustAsHtml('<div dynamic="htmlString">dddzzz</div>')); Any ideas on workarounds etc...

@Suraj Dalvi 2015-12-29 10:22:12

I use this directive but i am getting following error: Error: [$parse:lexerr] errors.angularjs.org/1.4.7/$parse/… Please help me into this?

@dzagorovsky 2016-09-27 17:33:32

@Murali Krishna 2017-01-13 16:49:13

This is working fine when I declare the HTML(with custom directive) on page load but custom directives are not loading as expected when I get the HTML string from the DB on click, I tried $timeout in the above directive but no use, can anyone suggest an idea?

Related Questions

Sponsored Content

9 Answered Questions

[SOLVED] Why does HTML think “chucknorris” is a color?

22 Answered Questions

[SOLVED] What are valid values for the id attribute in HTML?

  • 2008-09-16 09:08:52
  • Mr Shark
  • 412311 View
  • 1887 Score
  • 22 Answer
  • Tags:   html

3 Answered Questions

[SOLVED] Cannot display HTML string

30 Answered Questions

[SOLVED] How to create an HTML button that acts like a link?

24 Answered Questions

[SOLVED] HTML 5: Is it <br>, <br/>, or <br />?

  • 2009-12-22 13:39:08
  • Eikern
  • 1276211 View
  • 1902 Score
  • 24 Answer
  • Tags:   html html5

25 Answered Questions

[SOLVED] Retrieve the position (X,Y) of an HTML element

30 Answered Questions

[SOLVED] Convert HTML + CSS to PDF with PHP?

26 Answered Questions

[SOLVED] Redirect from an HTML page

7 Answered Questions

[SOLVED] Adding parameter to ng-click function inside ng-repeat doesn't seem to work

8 Answered Questions

Sponsored Content