Angular $watch 数组拼接

Angular $watch with array splice

我遇到了关于 Angular 中的观察者和数组拼接的问题。结合他们两个有一个非常奇怪的行为。所以我创建了一个小演示,其中包含显示正在发生的问题的日志。这是我正在使用的代码,或者您可以在 Plunker.

中看到它的工作原理

html:

<div class="logs">
    <div><b>LOGS:</b></div>
    <ul ng-repeat="watchEntry in watchersLogs track by $index">
      <li>{{watchEntry}}</li>
    </ul>
    <div><b>Watcher Count:</b> {{watchers.length}}</div>
</div>
<div class="item" ng-repeat="item in list" ng-init="InitItem()">
  <div class="item-title-wrapper">
    <span class="item-title">Item {{ $index + 1 }}</span>
    <button ng-click="AddNewItem()">Add New</button>
    <button ng-click="RemoveItem()">Remove</button>
  </div>
  <div class="field">
    <div>
      CREDIT:
      <input type="number" name="credit" ng-model="item.credit" />
    </div>
    <div>
      DEBIT:
      <input type="number" name="debit" ng-model="item.debit" />
    </div>
  </div>
</div>

js:

var app = angular.module("listApp", []);
app.controller("listController", function($scope) {
  // list with all data:
  $scope.list = [{
    credit: 2000,
    debit: 0
  }, {
    credit: 100000,
    debit: 1000
  }];

  // list containing all watchers:
  $scope.watchers = [];
  // logs containing all watcher event:
  $scope.watchersLogs = [];

  function SetWatcher(itemIndex) {
    $scope.watchers.splice(itemIndex, 0, $scope.$watch("list[" + itemIndex + "]", function(newValues, oldValues, scope) {
      $scope.watchersLogs.push("Item " + itemIndex + " watcher fired!");
    }, true));
  }

  $scope.InitItem = function() {
    // set a watcher for newly create item:
    SetWatcher(this.$index);
  }

  $scope.AddNewItem = function() {
    var newItem = {
      credit: 0,
      debit: 0
    };

    // put the item into the array:
    $scope.list.splice((this.$index + 1), 0, newItem);
  };

  $scope.RemoveItem = function() {
    // destroy the watcher:
    $scope.watchers[this.$index]();
    $scope.watchers.splice(this.$index, 1);
    // remove the item from the list:
    $scope.list.splice(this.$index, 1);
  };
});

css:

.logs {
  margin-bottom: 50px;
}

.item {
  margin-bottom: 25px;
}

因此,如您所见,当我初始化或向 $scope.list 数组添加新项目时,我正在为其分配一个新观察者。从日志中您可以看到每个观察者只在启动时被触发一次,这很好。但是,我的应用程序需要在您选择的或当前正在审查的项目之后添加该项目/例如如果您有 3 个项目并为第 2 个单击 添加新 按钮,它应该在第 3 个位置/添加一个新条目。这就是我使用 splice.

的原因

然而,这导致 $watch 表达式有时会被执行多次。所以,如果你在最后一个地方添加一个新条目,它只会为它触发一次 $watch 表达式。没关系。但是,如果您将它添加到中间的某处/例如2nd, 3rd, etc./, watcher 表达式不仅会被触发,它后面的所有其他项目也会被触发。我猜 splice 以某种方式更改了新创建的项目之后的项目的引用,这就是为什么 $watch 在发生这种情况时执行的原因。

我需要 $watch 表达式,因为我在更改值时进行一些计算,所以我认为我无法摆脱它们。正如我之前提到的,我也需要 splice 功能...那么,有什么解决办法吗?

提前致谢! :)

...更新...

我解决了问题!非常感谢@Deblaton Jean-Philippe 的帮助。所以,首先我创建了一个用于删除观察者的新函数:

function RemoveWatcher(itemIndex) {
  if ($scope.watchers[itemIndex]) {
    // destroy the watcher:
    $scope.watchers[itemIndex]();
    // remove it from the array as well:
    $scope.watchers.splice(itemIndex, 1);
  }
}

我在设置新观察者之前调用它,以确保它已被清除。我也在 RemoveItem 方法中调用它,但这里棘手的是我总是从数组中删除最后一个观察者条目,因为我正在拼接 $scope.list 数组和它更改字段的顺序。所以我在那里做的只是像这样调用 RemoveWatcher 方法:

RemoveWatcher($scope.watchers.length - 1);

这似乎工作得很好!您也可以检查更新的 Plunker。 :)

代码正在执行您要求他执行的操作。他正在观察您所询问的指数的价值。

如果你添加一个新手表并且索引已经注册,它会被触发两次。

如果您查看文档 here,您会看到 $watch() returns 一个函数。执行此功能将注销您的手表。

您需要做的是,当您添加新项目时,取消注册那里的手表。

也许这样的事情可以帮助你:

  function SetWatcher(itemIndex) {
    if($scope.watchers[itemIndex]) $scope.watchers[itemIndex]();
    $scope.watchers.splice(itemIndex, 0, $scope.$watch("list[" + itemIndex + "]", function(newValues, oldValues, scope) {
      $scope.watchersLogs.push("Item " + itemIndex + " watcher fired!");
    }, true));
  }