Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

injecting dynamic html doesn't seem to compile

Tags:

angularjs

I'm trying to solve a major problem I'm experiencing with angularJS in respect to using dynamically-added html that contains ng-controller.

Lets say that I want to add a div to the DOM that is a ng-controller, that blurts it's bound data out to the display. I can achieve this successfully as follows:

<!DOCTYPE html>
<html ng-app="myApp">
<head>
    <script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.4.7/angular.js"></script>
    <script src="https://code.jquery.com/jquery-2.1.4.min.js"></script>
    <script>
        var demoData = {
            'test1': 'one',
            'test2': 'two'
        };

        var myApp = angular.module('myApp', []);
        myApp.controller('TestCtrl', function ($scope) {
            $scope.demo = demoData;
        });
    </script>
</head>
<body>
    <div ng-controller="TestCtrl">
        {{demo}}
    </div>            
</body>
</html>

which outputs the following, as expected:

{"test1":"one","test2":"two"}

However, now lets say that the div actually has to be loaded dynamically, perhaps when a user presses a button. In this case, I'd replace the tag in the above example with the following:

<body>
    <button onclick="addDiv();">Click to add Div!</button>
    <script>
        function addDiv() {
            var newDiv = $('<div ng-controller="TestCtrl">{{demo}}</div>');
            $(document.body).append(newDiv);
        }
    </script>
</body>

which outputs the following when I click the button:

Click to add Div!
{{demo}}

So far, this makes sense; angular has already worked its way through the DOM, done it's thing, and finished. It's not been told about new stuff being added. So, if we look at the AngularJS manual, right at the bottom of this page, we find out how to tell it we've just added some stuff:

Sometimes you want to get access to the injector of a currently running Angular app from outside Angular. Perhaps, you want to inject and compile some markup after the application has been bootstrapped. You can do this using the extra injector() added to JQuery/jqLite elements. See angular.element.

This is fairly rare but could be the case if a third party library is injecting the markup.

In the following example a new block of HTML containing a ng-controller directive is added to the end of the document body by JQuery. We then compile and link it into the current AngularJS scope. var $div = $('{{content.label}}'); $(document.body).append($div);

angular.element(document).injector().invoke(function($compile) {
  var scope = angular.element($div).scope();
  $compile($div)(scope);
});

So... with this in mind, we update addDiv() function in our example as follows:

function addDiv() {
    var $newDiv = $('<div ng-controller="TestCtrl">{{demo}}</div>');
    $(document.body).append($newDiv);

    angular.element(document).injector().invoke(function ($compile) {
        var scope = angular.element($newDiv).scope();
        $compile($newDiv)(scope);
    });
}

And now when we run it, we should be golden right?
Nope.. we still get the following:

Click to add Div!
{{demo}}

sad panda is sad

Can you point out what I'm doing wrong, as no end of googling and reading the manual is helping, as everything seems to suggest I've got the code right!

like image 641
Sk93 Avatar asked Oct 19 '15 12:10

Sk93


2 Answers

Your code works as is when invoked from a function called from an Angular ng-click.

Since you seem to really want to avoid using Angular, and thus want a traditional onclick event handler, you need to wrap the changes into a call to $scope.$apply(): http://plnkr.co/edit/EeuXf7fEJsbBmMRRMi5n?p=preview

angular.element(document).injector().invoke(function ($compile, $rootScope) {
    $rootScope.$apply(function() {
        var scope = angular.element($newDiv).scope();
        $compile($newDiv)(scope);
    });
});
like image 55
JB Nizet Avatar answered Oct 23 '22 10:10

JB Nizet


Are you trying to compile newDiv, when the variable is $newDiv?

Edit: I think this is because you are using the injector and compile outside of an angular controller.

I have setup a plunkr at http://plnkr.co/edit/utQvgyR7d0jBgx5aEpJa?p=preview to demonstrate this.

The example uses a controller on the body of the page:

app.controller('InitialController', function ($scope, $injector){
    $scope.newBtn = function () {
      var $newDiv = $('<div ng-controller="TestCtrl">{{demo}}</div>');

      $injector.invoke(function ($compile) {
          var div = $compile($newDiv);
          var content = div($scope);
          $(document.body).append(content);
      });
    };
});

This is responsible for dynamically adding content and compiling that content with a second controller:

app.controller('TestCtrl', function($scope) {
  $scope.demo = "This is the div contents";
});

The second controller is responsible for the logic related to that content.

like image 3
Matt Waldron Avatar answered Oct 23 '22 08:10

Matt Waldron