70

假设我有一个带有大数组的 ng-repeat。

当 ng-repeat 运行时,它会将该数组的每个元素添加到一个独立的作用域中,并将数组本身放在一个作用域中。这意味着 $digest 检查整个数组是否有更改,最重要的是,它会检查该数组中的每个单独元素是否有更改。

将此 plunker视为我正在谈论的示例。

在我的用例中,我从不更改数组的单个元素,因此我不需要监视它们。我只会更改整个数组,在这种情况下 ng-repeat 会重新渲染整个表格。(如果我错了,请告诉我..)

在(比如说)1000 行的数组中,还有 1000 个我不需要评估的表达式。

如何在观察主数组的同时从观察者注销每个元素?

也许不是取消注册,我可以更好地控制我的 $digest 并以某种方式跳过每一行?

这个具体案例实际上是一个更普遍的问题的例子。我知道$watch 返回一个“取消注册”功能,但是当指令注册手表时,这无济于事,这在大多数情况下都是如此。

4

5 回答 5

89

拥有一个带有大型阵列的中继器,您不会观看每个项目。

您需要创建一个自定义指令,该指令接受一个参数和数组的表达式,然后在链接函数中您只需观察该数组,并且您将让链接函数以编程方式刷新 HTML(而不是使用ng-重复)

类似(伪代码):

app.directive('leanRepeat', function() {
    return {
        restrict: 'E',
        scope: {
           'data' : '='
        },
        link: function(scope, elem, attr) {
           scope.$watch('data', function(value) {
              elem.empty(); //assuming jquery here.
              angular.forEach(scope.data, function(d) {
                  //write it however you're going to write it out here.
                  elem.append('<div>' + d + '</div>');
              });
           });
        }
    };
});

......这似乎是一个痛苦的屁股。

替代hackish方法

您也许可以循环$scope.$$watchers并检查$scope.$$watchers[0].exp.exp它是否与您要删除的表达式匹配,然后通过简单的splice()调用将其删除。这里的 PITA 是Blah {{whatever}} Blah标签之间的内容将是表达式,甚至包括回车。

从好的方面来说,您可能只需遍历 ng-repeat 的 $scope 并删除所有内容,然后显式添加您想要的手表......我不知道。

无论哪种方式,这似乎都是一个黑客。

删除由 $scope.$watch 制作的观察者

您可以$watch使用调用返回的函数取消注册 a $watch

例如,$watch只有一次火灾:

var unregister = $scope.$watch('whatever', function(){ 
     alert('once!');
     unregister();
});

当然,您可以随时调用 unregister 函数……这只是一个示例。

结论:没有真正的好方法可以完全按照您的要求进行

但要考虑的一件事:它甚至值得担心吗?此外,将数千条记录分别加载到数十个 DOMElement 中真的是个好主意吗?深思熟虑。

我希望这会有所帮助。


编辑 2(删除了坏主意)

于 2012-11-30T19:53:01.037 回答
16

$watch 返回一个在调用时取消绑定 $watch 的函数。这就是“watchOnce”所需的全部内容:

var unwatchValue = scope.$watch('value', function(newValue, oldValue) {
  // Do your thing
  unwatchValue();
});
于 2016-02-22T18:07:50.353 回答
8

编辑:查看我发布的其他答案。

我已经以可分离的方式实现了 blesh 的想法。我的指令只是破坏了在每个项目ngOnce上创建的子范围。ngRepeat这意味着范围不会从其父母那里得到,scope.$digest并且观察者永远不会被执行。

JSFiddle 上的源代码和示例

指令本身:

angular.module('transclude', [])
 .directive('ngOnce', ['$timeout', function($timeout){
    return {
      restrict: 'EA',
      priority: 500,
      transclude: true,
      template: '<div ng-transclude></div>',
        compile: function (tElement, tAttrs, transclude) {
            return function postLink(scope, iElement, iAttrs, controller) {
                $timeout(scope.$destroy.bind(scope), 0);
            }
        }
    };
}]);

使用它:

      <li ng-repeat="item in contents" ng-once>
          {{item.title}}: {{item.text}}
      </li>

Note ng-once不会创建自己的范围,这意味着它可以影响同级元素。这些都做同样的事情:

  <li ng-repeat="item in contents" ng-once>
      {{item.title}}: {{item.text}}
  </li>
  <li ng-repeat="item in contents">
      <ng-once>
          {{item.title}}: {{item.text}}
      </ng-once>
  </li>
  <li ng-repeat="item in contents">
      {{item.title}}: {{item.text}} <ng-once></ng-once>
  </li>

请注意 ,这可能是个坏主意

于 2013-06-28T12:16:53.667 回答
3

您可以将bindonce指令添加到您的ng-repeat. 您需要从https://github.com/pasvaz/bindence下载它。

编辑:一些警告:

如果您{{}}在模板中使用插值,则需要将其替换为<span bo-text>.

如果您使用ng-指令,则需要用正确的bo-指令替换它们。

此外,如果您将bindonceandng-repeat放在同一个元素上bindonce,您应该尝试track byng-repeat

于 2013-11-25T14:20:50.207 回答
2

如果您正在使用angularjs 1.3或以上,您可以使用单一绑定语法作为

<li ng-repeat="item in ::contents">{{item}}</li>

这将绑定该值,并在第一个摘要周期运行并且该值第一次从undefined变为时删除观察者defined

一个非常有用的博客

于 2016-01-31T08:29:33.103 回答