AngularJS 监听 DOM 变化

35

我有一个auto-carousel指令,它可以遍历链接元素的子元素

然而,这些子元素尚未在DOM中加载,因为它们的ng-if表达式尚未被解析。

我该如何确保父指令知道其DOM树已经发生了变化?

        <ul class="unstyled" auto-carousel>
          <li class="slide" ng-if="name">{{name}}</li>
          ...
          <li class="slide" ng-if="email">{{email}}</li>
        </ul>

我可以使用 $timeout 但这感觉不可靠。我也可以使用 ng-show 替代 ng-if,但那并没有回答问题也不是我所需要的。


1
根据AngularJS文档,子指令应该在父指令之前加载,因此ng-if指令会在父指令之前加载。因此,解析顺序不是您的问题。也许您的数据是异步可用的,因此ng-if指令在链接时间中未被解析。 - Alborz
@Alborz 非常有趣。你有链接吗?如果是这样,我有一种感觉指令在子元素的模型填充之前被链接了,我会进行测试并回复你。别忘了我的链接! - pilau
@Alborz 链接函数绝对是在模型填充之后发生的。我甚至使用了额外的 $scope.$apply() - pilau
我创建了一个示例。您将看到子链接在父链接之前执行。http://jsbin.com/udaTEraM/1/edit - Alborz
1
@Alborz 感谢您抽出时间来设置它。但是我在 ng-if$watch 处理程序中添加了一个 console.log,很明显它是在父链接器之后执行的。有没有办法在子元素更改后调用父链接器?比如,监视 DOM 更改?(监视模型更改是个问题,因为这个指令是通用的,到处都在使用) - pilau
@Alborz,我明白了伙计,看看我的答案吧 :) - pilau
5个回答

77

所以这就是我最终做的事情:

我发现你可以将一个函数传递给$scope.$watch。从那里开始,很容易返回您想要监视其更改的表达式的值。它将完全像在作用域上传递键字符串一样工作。

link: function ($scope, $el, $attrs) {
  $scope.$watch(
    function () { return $el[0].childNodes.length; },
    function (newValue, oldValue) {
      if (newValue !== oldValue) {
        // code goes here
      }
    }
  );
}

我关注的是childNodes而不是children,因为childNodes列表包含元素、文本节点和注释,这非常宝贵,因为Angular使用注释占位符来表示指令,如ng-repeatng-ifng-switchng-include,这些指令执行插入并改变DOM,而children只包含元素。


1
这很好!我遇到了这样一种情况,需要在父元素内添加或删除input元素时每次运行一个函数。所以, $scope.$watch(function () { return $(".parent input").length; }, ...。这个方法非常有效,但我对性能有些担忧... - Elise
8
好的,你需要在每个$digest循环中至少调用一个jQuery查找(在这种情况下,还是一个非常糟糕的查找)。性能方面这相当不好。你可以缓存节点,然后使用jQuery的“find()”来改进它。 - pilau
2
我这样做,而不是像这样监听contenteditable的内容长度: scope.$watch(function () { return element[0].innerHTML.length;}, function (n, o) { /执行操作/ }); - Simon Dragsbæk
1
@SimonPertersen,这对于 contenteditable 来说是一个聪明的方法! - pilau
建议使用angular.equals(newValue, oldValue)比较newValueoldValue,即使在本答案的用例中可比较的值是纯数字。 - Nik Sumeiko

19

如果您需要监视元素DOM中更深层次的任何更改,则MutationObserver是可行的方法:

.directive('myDirective', function() {
    return {
        ...
        link: function(scope, element, attrs) {
            var observer = new MutationObserver(function(mutations) {
                // your code here ...
            });
            observer.observe(element[0], {
                childList: true,
                subtree: true
            });
        }
    };
});

7
有趣,但需要使用IE 11。 - IProblemFactory

5
我为这个angular-dom-events创建了一个指令模块。
在您的情况下,您可以:
    <ul class="unstyled" auto-carousel>
      <li class="slide" ng-if="name" dom-on-create="nameCreated()">{{name}}</li>
      <li class="slide" ng-if="email" dom-on-destroy="emailDestroyed()">{{email}}</li>
    </ul>

目前仅支持dom-on-createdom-on-destroy,但它比已接受的答案具有更好的性能,因为它只会针对每个dom事件触发一次,而不是反复检查$watch回调函数。


正是我所需要的。谢谢! - Simon Thum
如果您可以修改您的子元素,那就很好;但如果您在等待第三方元素出现,那就不太好了。 - Mario Levrero

2

虽然我认为这并不符合Angular的建议,但你可以使用ng-init来触发元素的初始化:

<ul class="unstyled" auto-carousel>
    <li class="slide" ng-if="name" ng-init="recheck()">{{name}}</li>
    <li class="slide" ng-if="email" ng-init="recheck()">{{email}}</li>
</ul>

您IP地址为143.198.54.68,由于运营成本限制,当前对于免费用户的使用频率限制为每个IP每72小时10次对话,如需解除限制,请点击左下角设置图标按钮(手机用户先点击左上角菜单按钮)。 - commonpike
这是唯一对我起作用的方法。我需要在动态插入表单到DOM后运行一些代码。 - Jeff Dunlop

0
你可以尝试在链接函数中首先编译指令内容。例如:
angular.module('myApp').directive('autoCarousel', ['$compile', function ($compile) {

    return {
        templateUrl: 'views/auto-carousel.html',
        restrict: 'A',
        replace: true,
        link: function (scope, element, attr) {
            $compile(element.contents())(scope);

            // your code goes here
        }
    }
}]);

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接