在页面加载后动态地使用html注入新的角度js控制器

时间:2016-08-11 11:24:39

标签: javascript angularjs

我一直在研究一个角度js站点,它从webservice / api获取数据。一个api返回html和angular js代码以动态添加控制器或我们想要添加新的任何新的角度组件。 该字符串将以api响应

<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>

现在我有一个指令将此字符串绑定到页面

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

指令&#39; 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应该将字符串转换为角度组件,但是由于此错误而失败。

  

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

5 个答案:

答案 0 :(得分:14)

  

从Angular 1.5开始,有一个新的angular.component工具。此答案使用组件一词来引用Angular提供的所有可能工具(例如指令,过滤器等),包括angular.component

首先让我从AngularJS开始设置的方式开始:

  1. 加载javascript文件后,您可以看到所有角度代码都以angular.moduleangular.controllerangular.directive等开头。所有这些代码都会收到一个函数参数(angular.module除外,它接收名称和依赖项列表)。此时,这些组件不会被创建,只是注册了。

  2. 一旦Angular注册了所有模块和组件,就可以使用ng-app指令或angular.bootstrap手动为bootstrapped。两种方法都接收一个字符串作为参数,这是根模块的名称。使用该根模块,Angular深入了解它的依赖关系(可以看作是依赖关系树),并开始从它的叶子(没有依赖关系的模块)加载组件到根模块的组件。

  3. 对于每个模块,Angular按照一定的顺序构建它们,从常量开始,然后是提供程序,然后按照它们注册的顺序运行配置块,然后加载值,然后是工厂/服务,然后是指令,最后运行按照他们注册的顺序阻止(我不完全确定订单,我建议您仔细检查)

  4. 最后,在设置好所有内容之后,它会密封&#39;应用程序,所以没有其他任何东西可以注册。

  5. 错误意味着您要两次引导您的应用程序。使用ng-app指令并调用angular.bootstrap或只是多次调用angular.bootstrap时,就会发生这种情况。

    正如文档在手动初始化部分中所述:

      

    您应该在加载或定义后致电angular.bootstrap()   你的模块。您无法添加控制器,服务,指令等   在应用程序引导之后。

    因此,为了动态加载新控制器,您应该在引导过程之前注册它们。您可以通过从API中检索所需的数据来实现这一点(因为Angular尚未设置,您需要任何其他工具,如JQuery),使用JavaScript的标准{{1执行代码函数(在请求的承诺中执行此操作),然后使用eval手动引导AngularJS。

    为了实现这一点,您使用angular.bootstrap执行的代码应该是纯粹的javascript,我建议您通过更改API的响应(如果可能)将其与HTML分开,或者以编程方式执行此操作得到回应。

    此外,如果您需要多次执行此操作,请确保在注册所需的所有组件之前,使用eval执行的代码不会引导Angular。

答案 1 :(得分:2)

为什么使用scope.$eval而不是$compile服务来编译模板字符串。

   link: function (scope, ele, attrs) {
      scope.$watch(attrs.html, function(html) {
        $compile(ele.contents())(scope);
      });
    }

答案 2 :(得分:2)

试试这个:

    app.directive('dynamic', [ '$compile',
function ($compile) {
    return {
        restrict: 'A',
        replace: true,
        link: function (scope, ele, attrs) {
            scope.$watch(attrs.dynamic, function (html) {
                ele.html(html);
                $compile(ele.contents())(scope);
            });
        }
    };
}]);

您可以将整个字符串放在$scope.myString的js和HTML <div dynamic="myString"></div>中。它应该编译并呈现所有内容。

答案 3 :(得分:0)

我找到了一个可能的解决方案,在引导之前我不需要了解控制器:

// Make module Foo and store $controllerProvider in a global
var controllerProvider = null;
angular.module('Foo', [], function($controllerProvider) {
    controllerProvider = $controllerProvider;
});
// Bootstrap Foo
angular.bootstrap($('body'), ['Foo']);

// .. time passes ..

// Load javascript file with Ctrl controller
angular.module('Foo').controller('Ctrl', function($scope, $rootScope) {
    $scope.msg = "It works! rootScope is " + $rootScope.$id +
        ", should be " + $('body').scope().$id;
});
// Load html file with content that uses Ctrl controller
$('<div id="ctrl" ng-controller="Ctrl" ng-bind="msg">').appendTo('body');

// Register Ctrl controller manually
// If you can reference the controller function directly, just run:
// $controllerProvider.register(controllerName, controllerFunction);
// Note: I haven't found a way to get $controllerProvider at this stage
//    so I keep a reference from when I ran my module config
function registerController(moduleName, controllerName) {
    // Here I cannot get the controller function directly so I
    // need to loop through the module's _invokeQueue to get it
    var queue = angular.module(moduleName)._invokeQueue;
    for(var i=0;i<queue.length;i++) {
        var call = queue[i];
        if(call[0] == "$controllerProvider" &&
           call[1] == "register" &&
           call[2][0] == controllerName) {
            controllerProvider.register(controllerName, call[2][1]);
        }
    }
}
registerController("Foo", "Ctrl");
// compile the new element
$('body').injector().invoke(function($compile, $rootScope) {
    $compile($('#ctrl'))($rootScope);
    $rootScope.$apply();
});

Fiddle。唯一的问题是你需要存储$ controllerProvider并在不应该使用它的地方使用它(在引导程序之后)。在注册之前,似乎没有一种简单的方法来获取用于定义控制器的函数,因此我需要遍历模块的_invokeQueue,这是未记录的。

更新:要注册指令和服务,而不是$ controllerProvider.register,只需分别使用$ compileProvider.directive和$ provide.factory。同样,您需要在初始模块配置中保存对这些的引用。

UDPATE 2:这是一个小提琴,可以自动注册所有加载的控制器/指令/服务,而无需单独指定它们。

答案 4 :(得分:0)

您可以将javascript eval用于eval js代码,将$compile用于html代码。我已经设置了一个带有完整测试代码的plunker。这是链接。

  

https://plnkr.co/edit/9sJw8ua1n5ItnHONwGmS