我一直在研究一个角度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
答案 0 :(得分:14)
从Angular 1.5开始,有一个新的
angular.component
工具。此答案使用组件一词来引用Angular提供的所有可能工具(例如指令,过滤器等),包括angular.component
。
首先让我从AngularJS开始设置的方式开始:
加载javascript文件后,您可以看到所有角度代码都以angular.module
,angular.controller
,angular.directive
等开头。所有这些代码都会收到一个函数参数(angular.module
除外,它接收名称和依赖项列表)。此时,这些组件不会被创建,只是注册了。
一旦Angular注册了所有模块和组件,就可以使用ng-app
指令或angular.bootstrap
手动为bootstrapped。两种方法都接收一个字符串作为参数,这是根模块的名称。使用该根模块,Angular深入了解它的依赖关系(可以看作是依赖关系树),并开始从它的叶子(没有依赖关系的模块)加载组件到根模块的组件。
对于每个模块,Angular按照一定的顺序构建它们,从常量开始,然后是提供程序,然后按照它们注册的顺序运行配置块,然后加载值,然后是工厂/服务,然后是指令,最后运行按照他们注册的顺序阻止(我不完全确定订单,我建议您仔细检查)
最后,在设置好所有内容之后,它会密封&#39;应用程序,所以没有其他任何东西可以注册。
错误意味着您要两次引导您的应用程序。使用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。这是链接。