AngularJS:在包含带有 templateurl 的指令的 html 上使用 $compile

Posted

技术标签:

【中文标题】AngularJS:在包含带有 templateurl 的指令的 html 上使用 $compile【英文标题】:AngularJS: Using $compile on html that contains directives with templateurl 【发布时间】:2015-01-16 11:10:36 【问题描述】:

我有一个遗留应用程序,它通过 jQuery 将一些内容插入到 DOM 中。我希望代码库的遗留部分负责编译它插入到 DOM 中的 html

我可以让它使用$compile 编译初始html,但是不会编译由指令的模板或templateUrl 添加的任何DOM 元素,除非我从指令本身中调用$scope.$apply()

我在这里做错了什么?

小提琴链接: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)

输出

scope 003 loaded from html
scope 005 loaded from xhr (auto-applied)
scope $id loaded from source

更新:解决方案适用于具有模板属性的指令,但不适用于 templateUrl

所以,我应该一直在编译 dom 节点,而不是 HTML 字符串。但是,如果指令包含 templateUrl,则此更新的小提琴显示相同的失败行为:

http://jsfiddle.net/trz80n9y/3/

【问题讨论】:

【参考方案1】:

您可能已经意识到,您需要调用 $scope.$apply() 来更新作用域值中的 bindings

但您无法在异步函数中执行此操作的原因是您正在针对#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 开始。您只需要消化您链接的一个作用域,因此只需消化那个作用域就足够了(而且速度更快,对于任何具有大量作用域的合理大小的应用程序)。

Forked fiddle

更新:Angular 可以编译字符串和分离的 DOM 节点

我刚刚检查过,OP 实际上是正确的,假设 Angular 可以编译 HTML 字符串或分离的 DOM 节点就好了。但是您需要做的是确保您确实将编译后的 node 附加到 DOM,而不仅仅是 HTML。这是因为 Angular 将范围和绑定信息等内容作为 jQuery/jQueryLite 数据存储在 DOM 节点* 上。因此,您需要使用该额外信息附加整个节点,以便 $digest() 可以工作。

因此,完成这项工作的另一种方法是将上述 OP 代码的相同部分更改为:

target.appendChild($compile(html)($scope)[0]);
$scope.$digest()

* 从技术上讲,它存储在内部 jQuery 数据缓存中,缓存键存储在 DOM 节点本身。

【讨论】:

是的,这完全有道理。出于某种原因,我认为 angular 会编译分离的 dom 节点,尽管现在我大声说我们只是在谈论一个字符串,而不是 DOM 节点。感谢 $digest vs $apply 的简洁描述:) @NeilSarkar 事实证明你是对的,它能够编译分离的 DOM 节点。我更新了我的答案,解释了为什么你的方法没有达到目标。 我建议在生产应用程序中调用 $scope.$digest() 时要格外小心。生成摘要的正确方法是调用 $apply。 这是一个有趣的立场,Enzey。如果您愿意,我很乐意在chat 中参与讨论。 @GregL 所以,事实证明您的解决方案适用于模板案例,但不适用于 templateurl。看到这个更新的小提琴。我现在正在挖掘角度源以查看 templateUrl 的实际工作原理,但如果您能想到这个更新问题的解决方案,我将不胜感激:jsfiddle.net/trz80n9y/3【参考方案2】:

先将元素附加到目标,然后编译它。

html = angular.element(html);
target = angular.element(target);
target.append(html);
html = $compile(html)($scope)

http://jsfiddle.net/f3dkp291/16/

【讨论】:

这基本上是我的答案的 TL;DR 版本。 :-) 可悲的是,是的。 缺少摘要,不是吗?

以上是关于AngularJS:在包含带有 templateurl 的指令的 html 上使用 $compile的主要内容,如果未能解决你的问题,请参考以下文章

带有 AngularJS 的某种俄罗斯娃娃容器的动态编辑表单

带有回调的AngularJS动画,等待下一个动画开始

使用带有 angularjs 和 jquery mobile 的翻转切换开关

带有扩展类的 AngularJS 动态样式

将带有HTML标签的输出从AngularJs传递到HTML [重复]

带有svg图像模板的angularjs