一尘不染

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

angularjs

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

我可以使用来编译初始html
$compile,但是除非我$scope.$apply()从指令内部调用,否则不会编译指令模板或templateUrl添加的任何DOM元素。

我在这里做错了什么?

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


阅读 238

收藏
2020-07-04

共1个答案

一尘不染

正如您可能意识到的那样,您需要调用$scope.$apply()它以{{bindings}}从范围值更新。

但是您无法在异步函数中执行此操作的原因是,您正在针对的现有范围编译HTML
#target,然后尝试仅追加HTML。那是行不通的,因为您需要在DOM中拥有已编译的节点,要么通过使用jQuery
.append()或类似方法附加整个已编译的节点,要么innerHTML先设置DOM
,然后再编译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节点。但是,您需要做的是确保将编译后的 节点
实际附加到DOM,而不仅仅是HTML。这是因为Angular将诸如范围和绑定信息之类的内容存储为DOM节点*上的jQuery /
jQueryLite数据。因此,您需要在整个节点上附加这些额外的信息,以便$digest()可以工作。

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

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

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

2020-07-04