如何“取消监视”表达式


70

说我有一个大阵列的ng-repeat。

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

看到这个矮人是我所谈论的例子。

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

在(例如)1000行的数组中,那是我不需要再计算的1000个表达式。

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

也许不用注销,我可以更好地控制$ digest并以某种方式跳过每一行?

这个具体案例实际上是一个更普遍问题的例子。我知道$ watch返回一个'deregisteration'函数,但这在大多数情况下在指令注册手表时没有帮助。

Answers:


89

要拥有一个大阵列的中继器,您不必看就可以观看所有项目。

您需要创建一个自定义指令,该指令使用一个参数和表达式到数组,然后在链接函数中仅监视该数组,然后让链接函数以编程方式刷新HTML(而不是使用ng-repeat)

类似于(伪代码):

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>');
              });
           });
        }
    };
});

...这似乎是一种痛苦。

替代骇客方法

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

从好的方面来说,您也许可以通过ng-repeat的$ scope循环并删除所有内容,然后显式添加所需的手表……我不知道。

无论哪种方式,似乎都像是骇客。

删除$ scope制造的观察者。

您可以$watch通过$watch调用返回的函数注销a :

例如,一次$watch只能开火:

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

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

结论:确实没有一种很好的方法可以完全按照您的要求进行

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

希望对您有所帮助。


编辑2(删除坏主意)


谢谢blesh,很好的回答。我将尝试这些选项,并将以最有效的方式进行响应。回复:甚至值得担心-在Chrome中很好,但在IE中却使我们丧命。(当前)我们正在使用无限滚动下拉更多记录,因此在大多数情况下,我们在初始负载上具有20位左右的速度就足够且快速。但是,如果用户滚动滚动,则可能会有很多记录。
罗伊·特鲁伊洛夫

4
没关系... IE中的所有内容都使我丧命。;)
Ben Lesh 2012年

我建议使用某种只能在DOM中放入滚动显示内容的系统。也许尝试ng-grid github.com/angular-ui/ng-grid
Andrew Joslin

1
@blesh-您的上一次编辑是否可以解决问题?我知道它不会更新UI,但是仍然可以,但是手表中的每个行对象都可以,不是吗?
Roy Truelove

@RoyTruelove,是的,我想你是对的。可能唯一确定的选择是创建自己的指令,该指令可以为您重复操作。
Ben Lesh

16

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

var unwatchValue = scope.$watch('value', function(newValue, oldValue) {
  // Do your thing
  unwatchValue();
});

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>

注意 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>

请注意, 这可能不是一个好主意


3

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

编辑:一些警告:

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

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

另外,如果你把bindonceng-repeat在相同的元素,你应该尝试或者移动bindonce到父元素(见https://github.com/Pasvaz/bindonce/issues/25#issuecomment-25457970)或添加track by到您ng-repeat


我已将其插入显示20列的数据网格之一,但在100行之后仍然滞后。如果我将列中的数据切换为某些文本,例如“数据”,则它不会滞后。
亚当·K·迪恩

2
我使用的是bo-text,但仍然遇到问题。遗憾的是,没有异步ng-repeat仅在触发事件时才更新。这会使生活变得轻松很多!
亚当·K·迪安

2

如果您使用的是angularjs 1.3以上版本,则可以将单一绑定语法用作

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

这将绑定的值,将删除从曾经的第一摘要周期运行观察家和值的变化undefineddefined首次。

关于此的非常有用的博客

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.