Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Inject new angular js controller with html dynamically after page loaded

I have been working on a angular js site, which get the data from webservice/api. One api returns html and angular js code to dynamically add conroller or whatever new angular component we want to add new. This string will come in api response

<div id="homecontainer" class="flex-center p-page" loader style="overflow:hidden;">
    <div class="column-1">
        <div class="grid m-0 col-xs-12">
            <div ng-repeat="Widget in V3Widgets track by $index" class="grid-item">
                <div class="grid-sizer"></div>
             {{Widget}}
            </div>


        </div>
        <div ng-controller="WelcomeController">
            {{greeting}}
        </div>
        <script>
            var app = angular.module('demo', [])
                //RestService is Other Module Which is Already Working fine 
            .controller('WelcomeController', function ($scope,RestService) {
                $scope.greeting = 'Welcome!';
            });
            angular.bootstrap(document, ['demo']);
        </script>
    </div>
</div>

Now i have a directive to bind this string to page

<renderdynamicwidgethtml ng-if="Widget.Id==null && Widget.Html!=null" html="Widget.Html"/>

Directive's js

.directive('renderdynamicwidgethtml', ['$compile', function ($compile) {
    return function (scope, element, attrs) {
        scope.$watch(
          function (scope) {
              return scope.$eval(attrs.html);
          },
          function (value) {
              element.html(value);
              $compile(element.contents())(scope);
          }
       );
    };
}])

scope.$eval should convert the string to angular components, but it failed with this error.

[ng:btstrpd] http://errors.angularjs.org/1.3.17/ng/btstrpd?p0=document

like image 502
Akshay Bohra Avatar asked Aug 11 '16 11:08

Akshay Bohra


Video Answer


2 Answers

Since Angular 1.5, there's a new angular.component tool. This answer uses the word Component to make a reference to all the possible tools Angular provides (such as directives, filters, etc), including angular.component.

First let me start with the way AngularJS sets everything up:

  1. When the javascript files are loaded, you can see all the angular code starts with angular.module, angular.controller, angular.directive, etc. And all those receive a function as a parameter (except angular.module, which receives a name and a list of dependencies). At this moment, those components are not created, just registered.

  2. Once Angular has registered all the modules and components, it is ready to be bootstrapped, either with the ng-app directive or manually with angular.bootstrap. Both methods receive a string as a parameter, which is the name of the root module. Using that root module, Angular looks deeply into it's dependencies (which can be seen as a tree of dependencies), and starts loading the components from it's leaves (modules that do not have dependencies) up to the root module's components.

  3. For each module, Angular builds them in a certain order, starting from constants, then providers, then running config blocks in the order they were registered, then loading values, then factories/services, then directives and finally run blocks in the order they were registered (I'm not completely sure about the order, I suggest you double check)

  4. Finally, after everything is setup, it's 'seals' the application, so nothing else can be registered.

The error means that you are bootstraping your application twice. This can happen when using the ng-app directive and calling angular.bootstrap, or just calling angular.bootstrap more than once.

As the documentation says in the manual initialization section:

You should call angular.bootstrap() after you've loaded or defined your modules. You cannot add controllers, services, directives, etc after an application bootstraps.

So, in order to load new controllers dynamically, you should register them before the bootstrap process. You can do this by retrieving the data you need from your API (you'd need any other tool like JQuery since Angular isn't set up yet), executing the code with JavaScript's standard eval function (do this inside the request's promise), and then bootstraping AngularJS manually with angular.bootstrap.

For this to work, the code you execute with eval should be purely javascript, I recommend separating it from the HTML either by changing the API's response (if possible), or doing it programmatically after getting the response.

Also, if you need to do this more than once, make sure that the code execute with eval doesn't bootstrap Angular before registering all the components you need.

like image 86
Andrés Esguerra Avatar answered Oct 11 '22 17:10

Andrés Esguerra


Why are you using scope.$eval instead of $compile service which is intended to compile your template string.

   link: function (scope, ele, attrs) {
      scope.$watch(attrs.html, function(html) {
        $compile(ele.contents())(scope);
      });
    }
like image 44
Thalaivar Avatar answered Oct 11 '22 17:10

Thalaivar