53

I am working with angularjs 1.2.0-rc.3. I'd like to include html code into a template dynamically. For that I use in the controller :

html = "<div>hello</div>";
$scope.unicTabContent = $sce.trustAsHtml(html);

In the template I got :

<div id="unicTab" ng-bind-html="unicTabContent"></div>

It works fine for regular html code. But when I try to put angular template it is not interpreted, it is just included in the page. For example I'd like to include :

<div ng-controller="formCtrl">
    <div ng-repeat="item in content" ng-init="init()">
    </div>
</div>

Thanks a lot

Clement Roblot
  • 879
  • 2
  • 8
  • 15
  • Yes, that is not exactly how angular wants to work. If you want to add html dynamically, look into the `$complie` service. – Davin Tryon Nov 01 '13 at 11:53
  • 2
    why do not you use ngScript to create the about template and then use an ng-include,ng-src to add it – Vinod Louis Nov 01 '13 at 11:55

8 Answers8

80

This solution doesn't use hardcoded templates, and you can compile Angular expressions embedded within an API response.


Step 1. Install this directive: https://github.com/incuna/angular-bind-html-compile

Step 2. Include the directive in the module.

angular.module("app", ["angular-bind-html-compile"])

Step 3. Use the directive in the template:

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

Result:

Controller Object

 $scope.letter = { user: { name: "John"}}

JSON Response

{ "letterTemplate":[
    { content: "<span>Dear {{letter.user.name}},</span>" }
]}

HTML Output =

<div bind-html-compile="letterTemplate.content"> 
   <span>Dear John,</span>
</div>

For reference sake, here's the relevant directive:

(function () {
    'use strict';

    var module = angular.module('angular-bind-html-compile', []);

    module.directive('bindHtmlCompile', ['$compile', function ($compile) {
        return {
            restrict: 'A',
            link: function (scope, element, attrs) {
                scope.$watch(function () {
                    return scope.$eval(attrs.bindHtmlCompile);
                }, function (value) {
                    element.html(value);
                    $compile(element.contents())(scope);
                });
            }
        };
    }]);
}());
Ryan.lay
  • 1,741
  • 2
  • 17
  • 30
  • 1
    This jsfiddle helps illustrate the compiling difference between `ng-bind-html` and `bind-html-compile`: http://jsfiddle.net/HB7LU/14557/ – Henry Blyth Jun 24 '15 at 17:38
  • 3
    I think I hit this question three different times before I realized how simply this would solve my issue. I'd upvote it twice if I could. – Dave Apr 01 '16 at 19:12
19

This is what I've made, no idea if it's the angular wayTM, but it works and is super simple;

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

So;

<div id="unicTab" dynamic="unicTabContent"></div>

Edit: I found the angular way, and it's super simple.

$templateCache.put('unicTabContent', $sce.trustAsHtml(html));
<div id="unicTab" ng-include="'unicTabContent'"></div>

Don't need to make your own directives or anything. But it's a bind-once sort of deal, it wont see changes made to your html like the custom directive does.

Hashbrown
  • 12,091
  • 8
  • 72
  • 95
  • 2
    I find this answer is the easiest to implement. – distante Nov 23 '16 at 08:29
  • hey why use $watch here? why not just replace the element with the compiled content? thanks btw. – ColacX Jul 17 '17 at 16:09
  • so that if the variable changes, that change will be reflected in the DOM on the next `digest`. If your variable would never change you can simplify this, sure, but whenever I put an answer up I try to keep it as generic and universally applicable as possible. For my specific case I think my variable was empty when the directive gets evaluated so I needed the `watch` for when the variable does eventually get populated. – Hashbrown Jul 18 '17 at 02:31
  • This worked much better for me! Especially if you are using a filter with the element as well, then this is definitely the way to go. I get a digest limit reached if using the angular-bind -html-compile thing – Nazariy Oct 09 '17 at 21:25
  • Any way I can make the ng-html-compile conditional? I mean attach/assign the directive only on a certain condition like - Grid row click and behave normally otherwise? – RedBottleSanitizer Oct 22 '21 at 17:08
2

As Vinod Louis says in his comment, the best way to do that was to use templates. I had to define a template outside of the regular code, for example I added that code inside of my index.html :

<script type="text/ng-template" id="unic_tab_template.html">
    <div ng-switch on="page">
        <div ng-switch-when="home"><p>{{home}}</p></div>
        <div ng-switch-when="form">
            <div ng-controller="formCtrl">
                <div ng-repeat="item in content">{{item.name}}:{{item.value}}</div>
            </div>
        </div>
        <div ng-switch-default>an error accured</div>
    </div>
</script>

This template is conditional, so depending on the value of $scope.page, it switches between the 3 templates (the third being an error handler). To use it I had :

<div id="unicTab" ng-controller="unicTabCtrl">
    <div ng-include="'unic_tab_template.html'"></div>
</div>

That way my page changes depending on the $scope inside of my unicTabCtrl controller.

To conclude the idea of inserting angularsjs template seams to be difficult to realize ($compile seams to be the solution, but I wasn't able to make it work). But instead you may use conditional templating.

Clement Roblot
  • 879
  • 2
  • 8
  • 15
  • 2
    I like this solution, but it would have been better if we could see a sample where the HTML is coming from somewhere else (remote JSON for example and thus not hard coded in the project). – Wouter May 04 '14 at 07:46
2

I was trying to do the same thing and came across this module.

http://ngmodules.org/modules/ng-html-compile

I just included it and then I was able to use "ng-html-compile" instead of "ng-bind-html"

nbarth
  • 324
  • 1
  • 5
2

My solution to similar problem in my current app without using template (not elegant, but working):

directive('ngBindHtmlCompile', ['$compile', function ($compile) {
    return {
        restrict: 'A',
        compile: function compile(tElement, tAttributes, transcludeFn) {
            return function postLink(scope, element, attributes) {
                scope.$watch(function() {
                    return scope.$eval(attributes.ngBindHtml);
                }, function(newValue, oldValue) {
                    $compile(element.children())(scope);
                });
            };
        }
    };
}]);

It requires ngBindHtml on the same element and compiles the element content after it changes with ngBindHtml.

<div id="unicTab" ng-bind-html="unicTabContent" ng-bind-html-compile></div>

ng-html-compile looks similar but at first glance it won't be recalculated when the template content is changing. But I haven't tried it.

Lukasz Frankowski
  • 2,955
  • 1
  • 31
  • 32
1

One way is use a directive for purpose of inserting custom templates that include angular expresssions

<div id="unicTab" unic-tab-content></div>
app.directive("unicTabContent",function(){
   return {
      restrict:"A",
      template:'{{unicTabContent}}'
   }
})
charlietfl
  • 170,828
  • 13
  • 121
  • 150
  • I like this idea, and it does include the code, but it is still not compiled. – Clement Roblot Nov 01 '13 at 13:22
  • 1
    works fine here as is http://plnkr.co/edit/sUOxHBUYIdQlAiM3Dy0V?p=preview Perhaps you have some nested directives or something else causing it not to work – charlietfl Nov 01 '13 at 16:51
  • 2
    But I'd like it to compile the code in the variable that you called test. For exemple [plunker](http://plnkr.co/edit/9c50iy?p=preview) display the
    and
    , which is not what I'd like.
    – Clement Roblot Nov 01 '13 at 17:20
  • 1
    OK..can do it with `link` callback of directive [Plunker](http://plnkr.co/edit/GRAfrI?p=preview) – charlietfl Nov 01 '13 at 17:28
  • 2
    Indeed it compile html, but not the angular statements. In [Plunker](http://plnkr.co/edit/X9vpcd?p=preview) when the test variable is directly injected the ng-click method is connected, but when injected using the directive it isn't. – Clement Roblot Nov 01 '13 at 17:43
  • several compile techniques can be used.... you keep adding to the initial question...why didn't you outline what you needed from the start? Why wouldn't you just use a directive template, rather than creating angular html in scope??? Suggest you explain your needs in more detail...as well as why you are using approach you are using and not using angular mehtodology – charlietfl Nov 01 '13 at 18:00
0

The code below is much simpler using Angular's built-in $interpolate and $sce objects. First inject the $interpolate and $sce Angular objects into your directive as you do anything custom you need in your directive.

amqApp.directive('myDir', ['$interpolate', '$sce', function ($interpolate,$sce ) {...}

Then create all your scoped variables found in your imported html expressions...

$scope.custom = 'Hello World';

Next use $interpolate to process your custom HTML and its expressions...then make sure you use the $sce object to trust it as HTML before binding...

var html = $interpolate('<b>{{custom}}</b>')($scope);    
$scope.data = $sce.trustAsHtml(html);

Finally, in your view, just make sure use an element with the "ng-bind" or "ng-bind-html" on it in your view display. I found the $sce piece wont display the HTML as HTML (sees it as text) if you don't bind it in your html template like this...

<span ng-bind-html="data"></span>

You should see in bold...

Hello World

I used this trick to import in text/HTML with custom angular {{expressions}} from a web.config.

Stokely
  • 12,444
  • 2
  • 35
  • 23
0

A lot simplified solution of @clement-roblot based on built-in templates.

Controller:

app.controller('TestCtrl', [
    '$scope',
    '$templateCache',
    function ($scope, $templateCache) {
        $templateCache.put('test.html', '2 + 2 = {{ 2 + 2 }}');
    }
]);

View:

<div ng-include="'test.html'"></div>
Stalinko
  • 3,319
  • 28
  • 31