31

Im trying to put some angular js template string inside an element, and expect an complied output. But that's not happening.

HTML

<div ng-controller="testController">
    <div ng-bind-html-unsafe="fruitsView"></div>
</div>

Controller:

function filterController($scope){
    ...
    $scope.arr = ["APPLE", "BANANA"];
    $scope.fruitsView = '<div><p ng-repeat="each in arr">{{each}}</p></div>';
}

The ouput is just {{each}}.

So how do i insert an angular js template string (here $scope.fruitsView) inside an element?

I have made a fiddle for this.

Rajkamal Subramanian
  • 6,884
  • 4
  • 52
  • 69
  • 4
    Why are you generating the HTML from your controller? – Joseph Silber Feb 13 '13 at 05:26
  • My use case for this problem: I'm using a 3rd party library (leaflet) where I want to insert some controls that react to changes in the Angular scope. I'd like to avoid to create the elements using the vanilla DOM API and change them using `$scope.$observe`, since that's basically the same thing as just registering normal event listeners. – fredrikekelund Jun 29 '15 at 07:08

1 Answers1

96

In this case, you don't want to just "insert HTML", but compile it. You can create DOM nodes using the $compile service.

var tpl = $compile( '<div><p ng-repeat="each in arr">{{each}}</p></div>' )( scope );

As you can see, $compile returns a function that takes a scope object as a parameter, against which the code is evaluated. The resultant content can be inserted into the DOM with element.append(), for example.

Important note: But under no circumstances does any DOM-related code belong in your controller. The proper place is always a directive. This code can easily be thrown into a directive, but I wonder why you are programmatically inserting the HTML at all.

Can you shed some light here so I can provide a more specific answer?

Update

Assuming your data comes from a service:

.factory( 'myDataService', function () {
  return function () {
    // obviously would be $http
    return [ "Apple", "Banana", "Orange" ];
  };
});

And your template comes from a service

.factory( 'myTplService', function () {
  return function () {
    // obviously would be $http
    return '<div><p ng-repeat="item in items">{{item}}</p></div>';
  };
});

Then you create a simple directive that reads in the provided template, compiles it, and adds it to the display:

.directive( 'showData', function ( $compile ) {
  return {
    scope: true,
    link: function ( scope, element, attrs ) {
      var el;

      attrs.$observe( 'template', function ( tpl ) {
        if ( angular.isDefined( tpl ) ) {
          // compile the provided template against the current scope
          el = $compile( tpl )( scope );

          // stupid way of emptying the element
          element.html("");

          // add the template content
          element.append( el );
        }
      });
    }
  };
});

Then from your view:

<div ng-controller="MyCtrl">
   <button ng-click="showContent()">Show the Content</button>
   <div show-data template="{{template}}"></div>
</div>

And in the controller, you simply tie it together:

.controller( 'MyCtrl', function ( $scope, myDataService, myTplService ) {
  $scope.showContent = function () {
    $scope.items = myDataService(); // <- should be communicated to directive better
    $scope.template = myTplService();
  };
});

And it should all work together!

PS: this is all assuming your template comes from the server. If it doesn't, then your template should be in the directive, which simplifies things.

Curtis
  • 3,931
  • 1
  • 19
  • 26
Josh David Miller
  • 120,525
  • 16
  • 127
  • 95
  • I want to insert an list (the same list template in my question) in the page, when the user clicks on the an element. Can we handle this use case without doing dom operations in the controller ? – Rajkamal Subramanian Feb 13 '13 at 05:50
  • +rajkamal Absolutely! So I can update my answer with more specific code, is the clicked element the same as the one into which the stuff gets inserted or is it a different one? – Josh David Miller Feb 13 '13 at 05:53
  • I kept running into this error: `Error: Argument 'scope' is required` using the `$compile` function. I found that I had called the function wrong using two arguments, like so: `$compile(tpl, scope);` and been completely blind for `$compile` being a function that returns a function, and in stead should call it like so: `$compile(tpl)(scope);`, as you are supposed to. Just posting this in case anyone else makes this mistake. – mlunoe May 08 '13 at 16:16
  • I see showData but don't see it being used anywhere. Missing something? – jcollum Jun 28 '13 at 21:46
  • 1
    @jcollum It's being used from the view; it's the "show-data" attribute on the div. AngularJS directives are snake-case in views and camelCase in JavaScript. – Josh David Miller Jun 28 '13 at 21:52
  • I thought that might be the case -- it's confusing though. Thanks for the clarification. – jcollum Jun 28 '13 at 21:57
  • This started to lead me in the right direction, but, in order to get it to finally work I had replace the part inside the `if(angular.isDefined(tpl)) {` ... with just two lines: (remove var el completely) `element.html(tpl); $compile(element.contents())(scope);` – Robert Noack Jun 27 '14 at 05:06
  • How is that simpler? It replaces a one line solution(compiling the html) with about 20 lines across multiple files. What a PITA – user3806049 Dec 14 '15 at 13:03
  • best solution ! – Sanjay Radadiya Nov 02 '17 at 10:15