53

Is there any way to add an element that is an Angular directive with jQuery methods like append() and have Angular do its compilation/linking to make it work as though you'd included the directive in the first place?

Example:

app.directive('myAngularDirective', [function () {
    ...
    // Lots of stuff in here; works when used normally but not when added via jQuery
});

$("body").append("<my-angular-directive />");

It currently just appends an empty DOM element called "my-angular-directive," but Angular doesn't kick in and do its magic.

Flip
  • 6,233
  • 7
  • 46
  • 75
blaster
  • 8,876
  • 11
  • 48
  • 77
  • My suspicion is that you'll need to tell Angular that the new element now exists, as it probably didnt exist on doc Ready if youre adding it dynamically – hammus Nov 18 '13 at 22:26
  • You really want to avoid doing any jquery if you can, but I ran into a similar problem not too long ago, here was [my question](https://stackoverflow.com/questions/19867554/bind-angularjs-to-newly-created-html-element-dynamically) and the correct answer that should be able to help you out. The short answer is using $compile. – Ty Danielson Nov 18 '13 at 22:39

5 Answers5

77

The right way to go is to use: $compile and in case your directive returns: directive definition object (which is btw. the recommended way to go) you can then call link function on it (to inject scope, for example).

$('body').append($compile("<my-angular-directive />")(scope));
scope.$apply(); 
artur grzesiak
  • 20,230
  • 5
  • 46
  • 56
17

A complete example, from the Angular documentation:

// Angular boilerplate
var app = angular.module("myApp", []);
app.controller("MyCtrl", function($scope) {
  $scope.content = {
    label: "hello, world!",
  };
});

// Wrap the example in a timeout so it doesn't get executed when Angular
// is first started.
setTimeout(function() {
  // The new element to be added
  var $div = $("<div ng-controller='MyCtrl'>new: {{content.label}}</div>");
  
  // The parent of the new element
  var $target = $("[ng-app]");

  angular.element($target).injector().invoke(function($compile) {
    var $scope = angular.element($target).scope();
    $target.append($compile($div)($scope));
    // Finally, refresh the watch expressions in the new element
    $scope.$apply();
  });
}, 100);
<script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.2.23/angular.min.js"></script>
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>

<div ng-app="myApp">
   <div ng-controller="MyCtrl">old: {{content.label}}</div>
</div>
David Wolever
  • 148,955
  • 89
  • 346
  • 502
2

Ideally you should avoid doing that.

However, if you really, really, really need to, then you can inject and use the $compile service followed by an element.append.

If your directive doesn't need access to a specific scope, then you can even assign the $compile and $rootScope service to window in the run function of your application's module and then use them from outside the angular context by creating a new scope ($rootScope.new()) and wrap the appending of element by using $rootScope.apply().

eabraham
  • 4,094
  • 1
  • 23
  • 29
musically_ut
  • 34,028
  • 8
  • 94
  • 106
2

The accepted answer didn't provide a complete example, here's one:

https://codepen.io/rhinojosahdz/pen/ZpGLZG

<body ng-app="app" ng-controller="Ctrl1 as ctrl1">
</body>
<script>
angular.module('app',[])
.controller('Ctrl1', ['$scope', '$compile', function($scope, $compile){
    var vm = this;
    vm.x = 123;
    $('body').append($compile("<hola x='ctrl1.x' />")($scope));
}])
.directive('hola', function(){
    return {
        template: '<div ng-click="vm.alertXFromGivenScope()">click me!</div>'
        ,scope: {
            x : '='
        }
        ,controller: function(){
            var vm = this;
            vm.alertXFromGivenScope = function(){
                alert(vm.x);                
            };
        }
        ,controllerAs: 'vm'
        ,bindToController: true
    }
})
<script>
ilovelamp
  • 739
  • 3
  • 9
  • 20
0

Try this

angular.element($("#appendToDiv")).append($compile("<my-angular-directive />")($scope));
Hari Das
  • 10,145
  • 7
  • 62
  • 59