How to bind an AngularJS controller to dynamically added HTML?
Asked Answered
T

6

14

For this scenario, I have a page of HTML with some AngularJS directives, controllers, etc.

Something like this:

<html>
<body>
  <div ng-controller="myCtrl">
     <ul><li ng-repeat="item in items">{{item.name}}</li></ul>
  </div>
  <div class="placeholder">
     ...new HTML here...
  </div>
</body>
</html>

Notice that there is no ng-app directive on the page. I am not relying on auto-bootstrapping, but rather I am using the manual bootstrapping method.

angular.bootstrap(document, ['myApp']);

First, I create the module which will be bootstrapped to the document. Then as a dynamically determined list of dependencies are loaded, I attach some services, controllers, etc. Once everything is ready I call the bootstrap method.

This all works fine, until JavaScript outside of AngularJS appends to the DOM at the ...new HTML here... location. The new HTML is not processed by AngularJS.

My understanding is that you need to call $scope.$apply() or $digest() or something to get AngularJS to recognize the new HTML. However, in my example there is no controller around the new HTML coming in. The incoming HTML may look like this:

  <div ng-controller="myOtherCtrl">
     <h2>{{model.title}}</h2>
  </div>

In other words, it is relying on a different controller in the app module.

  • I can't call the angular.bootstrap method again, because the page is already bound.
  • I can't call $scope.$apply from inside the controller because the whole point is that controller code isn't being called.
  • I don't see a way to get a reference to the controller in order to re-apply or refresh it.

I have read the Ifeanyi Isitor lazy loading post and the Ben Nadel post, yet they don't seem to address HTML loaded outside the context of an existing controller.

It isn't an option for me to use AngularJS directives to handle the DOM manipulation. It is a separate part of the architecture which uses ExtJS as the framework for injecting the new DOM elements. This means I can't use ngInclude for example.

It would seem like I could just call angular.module('myApp').$refresh(); or .$apply() but that isn't an option. What am I missing?

Trudi answered 18/12, 2013 at 6:59 Comment(2)
I don't have any experience using extJS and angular together, so I'm afraid I cant help you any further. Just one thing to keep in mind though, AngularJS and ExtJS are both frameworks that are designed to be the only framework a webapp needs. Using two frameworks means that every client has to download a lot more data, which is often problematic, especially on mobile devices. See if you can convert the ExtJS part of your app to angularjs or vice versa.Footrace
ExtJS only comes into play during the Authoring experience and I don't have the option to remove it. I would like to use Angular for its modularity, 2-way binding, testability, etc. All ExtJS does in this case is render some HTML into a spot in the DOM. Being able get Angular to recognize that the change happened seems to be the challenge.Trudi
A
10

I've faced the same issue, here's what I came up with :

<div id="mController" ng-controller="mainController">
</div>

<div id="ee">
  2nd controller's view should be rendred here
</div>

and calling setCnt() function will inject and compile the html, and it will be linked to the 2nd controller:

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

function setCnt() {
  // Injecting the view's html
  var e1 = angular.element(document.getElementById("ee"));
  e1.html('<div ng-controller="ctl2">my name: {{name}}</div>');
  
  // Compile controller 2 html
  var mController = angular.element(document.getElementById("mController"));
  mController.scope().activateView(e1);
}

app.controller("mainController", function($scope, $compile) {
  $scope.name = "this is name 1";

  $scope.activateView = function(ele) {
    $compile(ele.contents())($scope);
    $scope.$apply();
  };
});

app.controller("ctl2", function($scope) {
  $scope.name = "this is name 2";
});

here's an example to test this : https://refork.codicode.com/x4bc

hope this helps.

Afreet answered 25/3, 2015 at 21:19 Comment(1)
nice work, though $scope.$apply(); has no use in your example.Sandiesandifer
B
2

Lets take a small template

 var  template = '<div ng-controller = "someController">';
      template += '<All html you want to add>'; 
      template += '</div>';

Now if you want to add this template you have to do the following two things:

1) Instantiate your controller by $controller

2) Compile your template.

//this creates a new scope
var $scope = $rootScope.$new();  
//Controller initialize with $scope
$controller('someController',{$scope,$scope});   
var templateEl = angular.element(template);
//Now compile the template with scope $scope
$compile(templateEl)($scope);
angular.element('body').append(templateEL);
Bonbon answered 20/4, 2015 at 19:26 Comment(2)
plus one for the last templateEL typoArium
This is a nice answer, and the closest to what I was looking for, but lacked the important caveat that you must already be inside of the "angular world" to have access to things like "$rootScope" and "$controller". If you are trying to do this on a page where there is no other controller, or angular directives, (and maybe not even ng-app) then this reply will get you there: https://mcmap.net/q/830899/-angularjs-how-to-dynamically-add-html-and-bind-to-controllerTelescopium
S
1

You might want to use $compile service. That's what angular do at first place.

And also this guide: http://docs.angularjs.org/guide/compiler

Spectacle answered 18/12, 2013 at 7:43 Comment(0)
R
1

I had the same problem. This worked for me: https://docs.angularjs.org/api/ng/function/angular.injector

Randle answered 17/9, 2014 at 20:45 Comment(1)
And what did you do with the angular $injector ? More details would be appreciatedSubsonic
F
0

I think ng-include might help you, it loads a partial page into the element and compiles and processes it like any other part of your web page.

If This element is the main view of your web app, and you want to load different 'screens' into it, depending on the url, ng-view may come in handy.

Footrace answered 18/12, 2013 at 7:20 Comment(1)
Thanks, I updated the question to clarify what is injecting the DOM elements. I don't think I can rely on ng-include because it needs the src URL which it loads and injects. ng-view might be worth digging into. My only concern there is that I don't really know where the HTML will be injected into the DOM so unless I can put the ng-view directive on the body tag, I don't think it will work.Trudi
H
0

I followed this process and it worked for me:

// Some variables
var $controllerElement = angular.element('css-selector-to-the-controller-element');
var $appElement = angular.element('css-selector-to-ng-app-element');

// compiling and applying / digesting the scope.
$appElement.injector().invoke(function($compile) {
    var scope = $controllerElement.scope();
    $compile($controllerElement)(scope);
    scope.$apply();
});

Reference: Angular.injector

Humberto answered 13/6, 2016 at 20:32 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.