问题描述
我有一个遗留应用程序,它通过 jQuery 将一些内容插入到 DOM 中.我希望代码库的遗留部分负责编译它插入到 DOM 中的 html.
我可以使用 $compile
编译初始 html,但是任何由指令的模板或 templateUrl 添加的 DOM 元素都不会被编译,除非我调用 $scope.$apply()
来自指令本身.
我在这里做错了什么?
小提琴链接:http://jsfiddle.net/f3dkp291/15/
index.html
<debug source='html'></debug><div id="目标"></div>
application.js
angular.module('app', []).directive('debug', function() {返回 {限制:'E',模板:从 {{source}} 加载的范围 {{$id}}",链接:函数($scope,el,attrs){$scope.source = attrs.sourceif( attrs.autoApply ) {//这有效$scope.$apply()}},范围:真实}})//模拟一个 xhr 请求设置超时(函数(){var html = "<div><debug source='xhr (auto-applied)' auto-apply='1'></debug><br/><debug source='xhr'></debug></div>",target = document.getElementById('target'),$injector = angular.injector(['ng','app']),$compile = $injector.get('$compile'),$rootScope = $injector.get('$rootScope'),$scope = angular.element(target).scope();target.innerHTML = $compile(html)($scope)[0].outerHTML//这些什么都不做,我想从这里编译指令的模板.$scope.$apply()$scope.$root.$apply()angular.injector(['ng','app']).get('$rootScope').$apply()}, 0)
输出
scope 003 从 html 加载从 xhr 加载的范围 005(自动应用)从 {{source}} 加载的范围 {{$id}}
更新:解决方案适用于具有模板属性的指令,但不适用于 templateUrl
所以,我应该编译 dom 节点,而不是 HTML 字符串.但是,如果指令包含 templateUrl,则此更新的小提琴显示相同的失败行为:
http://jsfiddle.net/trz80n9y/3/
你可能已经意识到,你需要调用 $scope.$apply()
来更新 {{范围值中的绑定}}
.
但是您无法在异步函数中执行此操作的原因是您正在针对 #target
的现有范围编译 HTML,然后尝试仅附加 HTML.这是行不通的,因为您需要在 DOM 中拥有编译节点,或者通过使用 jQuery 的 .append()
或类似方法附加整个编译节点,或者通过设置 DOM innerHTML
首先,然后编译在 DOM 中的节点.之后,您可以在该范围内调用 $apply
,因为该指令已编译并且在 DOM 中,它将被正确更新.
换句话说,请按如下方式更改异步代码.
代替:
target.innerHTML = $compile(html)($scope)[0].outerHTML$scope.$apply()
改为:
target.innerHTML = html;$compile(target)($scope);$scope.$digest();
请注意,我执行的是 $digest()
而不是 $apply()
.这是因为 $apply()
会从 $rootScope
开始对每个范围进行摘要.您只需要消化您链接的那个作用域,因此只需消化那个作用域就足够了(而且速度更快,对于任何具有大量作用域的合理大小的应用).
更新:Angular 可以编译字符串和分离的 DOM 节点
我刚刚检查过,OP 假设 Angular 可以很好地编译 HTML 字符串或分离的 DOM 节点,实际上是正确的.但是您需要做的是确保您确实将编译后的 node 附加到 DOM,而不仅仅是 HTML.这是因为 Angular 将范围和绑定信息等内容存储为 DOM 节点上的 jQuery/jQueryLite 数据.因此,您需要附加整个节点以及这些额外信息,以便 $digest()
可以工作.
因此,进行这项工作的另一种方法是将 OP 代码的相同部分更改为:
target.appendChild($compile(html)($scope)[0]);$scope.$digest()
I have a legacy application that has some content inserted into the DOM via jQuery. I would like the legacy parts of the codebase to be responsible for compiling the html that it inserts into the DOM.
I can get it to compile the initial html using $compile
, but any DOM elements added by a directive's template or templateUrl are not compiled, unless I call $scope.$apply()
from within the directive itself.
What am I doing wrong here?
Link to fiddle: http://jsfiddle.net/f3dkp291/15/
index.html
<div ng-app="app">
<debug source='html'></debug>
<div id="target"></div>
</div>
application.js
angular.module('app', []).directive('debug', function() {
return {
restrict: 'E',
template: "scope {{$id}} loaded from {{source}}",
link: function($scope, el, attrs) {
$scope.source = attrs.source
if( attrs.autoApply ) {
// this works
$scope.$apply()
}
},
scope: true
}
})
// mimic an xhr request
setTimeout(function() {
var html = "<div><debug source='xhr (auto-applied)' auto-apply='1'></debug><br /><debug source='xhr'></debug></div>",
target = document.getElementById('target'),
$injector = angular.injector(['ng','app']),
$compile = $injector.get('$compile'),
$rootScope = $injector.get('$rootScope'),
$scope = angular.element(target).scope();
target.innerHTML = $compile(html)($scope)[0].outerHTML
// these do nothing, and I want to compile the directive's template from here.
$scope.$apply()
$scope.$root.$apply()
angular.injector(['ng','app']).get('$rootScope').$apply()
}, 0)
output
scope 003 loaded from html
scope 005 loaded from xhr (auto-applied)
scope {{$id}} loaded from {{source}}
Update: Solution works for directives with a template property, but not templateUrl
So, I should have been compiling dom nodes, not an HTML string. However, this updated fiddle shows the same failing behavior if the directive contains a templateUrl:
http://jsfiddle.net/trz80n9y/3/
As you probably realised, you need to call $scope.$apply()
for it to update the {{bindings}}
from the scope values.
But the reason you couldn't do it inside your async function was that you were compiling the HTML against the existing scope for #target
, but then trying to append just the HTML. That won't work, because you need to have the compiled node in the DOM, either by appending the entire compiled node using jQuery's .append()
or similar, or by setting the DOM innerHTML
first, then compiling the node that is in the DOM. After that, you can call $apply
on that scope and because the directive is compiled and in the DOM, it will be updated correctly.
In other words, change your async code as follows.
Instead of:
target.innerHTML = $compile(html)($scope)[0].outerHTML
$scope.$apply()
Change it to:
target.innerHTML = html;
$compile(target)($scope);
$scope.$digest();
Note that I did a $digest()
instead of $apply()
. This is because $apply()
does a digest of every single scope, starting from the $rootScope
. You only need to digest that one scope you linked against, so it is sufficient (and faster, for any reasonably sized app with lots of scopes) to just digest that one.
Update: Angular can compile strings and detached DOM nodes
I just checked, and the OP was actually correct in assuming that Angular can compile strings of HTML or detached DOM nodes just fine. But what you do need to do is make sure you actually append the compiled node to the DOM, not just the HTML. This is because Angular stores things like the scope and the binding information as jQuery/jQueryLite data on the DOM node. Thus you need to append the whole node, with that extra information, so that the $digest()
will work.
So an alternative way of having this work is to change the same portion of the OP's code as above to:
target.appendChild($compile(html)($scope)[0]);
$scope.$digest()
这篇关于AngularJS:在包含带有 templateurl 的指令的 html 上使用 $compile的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持!