简体   繁体   中英

Angularjs: buggy two way data binding

I am trying to create recursive grid layout using directives.

  1. There is a parent container(appliedgrids) which contains array of grids inside it.
  2. A grid contains array of columns inside it.
  3. Each column has two properties: span(width of column) and data (data inside column)
  4. Each column data contains either grid again or a widget. If it contains grid then it make recursive call to grid directive.

My problem is when I delete a grid using remove button inside it- it gets removed from appliedgrid container but two way data binding doesn't work as it should. In place of current grid, last grid gets removed from the UI.

Link- http://plnkr.co/edit/DzKIHKvJdLoZiYY3jgDx?p=preview

Steps to reproduce: 1) Click remove button on first grid, you will see that in place of first, second grid gets removed. While json data of appliedgrid contains second grid inside it. So two way binding of angular doesn't work as it supposed to.

I did a little thinking in my previous answer and it turns out it was not correct.

Firstly, do not use track by $index . It makes no sense in your case. track by is an optimisation of ng-repeat to correlate (potentially new) objects in the array that are "business-wise" equal with old objects in the array, so that it re-uses scopes and DOM elements in an effort to minimize DOM manipulation. That is: if you give ng-repeat a hint which new object in the new array is "equal" to an old object in the old array, it will reuse its scope and hopping that the new object is not dramatically different compared to the old one, less $watch callbacks will fire and less DOM updates will occur.

Your actual problem is that you are "statically" or "once-off" binding data with statements like:

$scope.gridIndex = $parse($attrs.gridIndex)($scope);
$scope.gridValues=$parse($attrs.appliedgrid)($scope);
$scope.gridParent=$parse($attrs.appliedgrids)($scope);

The first grid item is indeed removed from the array but ng-repeat does not remove its scope and DOM element because track by $index is used. But still, the new 0-index object (2nd, previously) is used to update the scope (the one created for the 1st object).

You do not see this reflecting to the UI because $scope.gridValues was evaluated in the beginning and is not evaluated again.

So, even though $scope.appliedgrid now points to [{span:12,data:[object]}] , $scope.gridValues still points to [{span:6,data:[object]},{span:6,data:[grid2]}] .

Removing track by $index solves the problem because ng-repeat tracks objects by reference so each object is associated with the same scope until it is removed from the array.

You can verify it with AngScope , a small Firebug-based scope inspector. You have to open it in a separate tab with "Launch the preview in a separate window" in order for it to work in plunker.

I tried to find a quick fix for it but there was no luck. I guess, you have to re-write it using isolated scopes and real 2-way binding.

Short answer: remove track by $index from ng-repeat .

Long answer: When you are write track by $index you're actually saying to ng-repeat that:

  • The 1st DOM element will be associated to an object that is tagged as the "0" object
  • The 2nd DOM element will be associated to an object that is tagged as the "1" object

When you remove the 1st object from the array, angular digests and finds out the following:

  • The 1st DOM element is still associated to an object tagged as the "0" object
  • The 2nd DOM element is not associated to any object, so it has to be removed

This is because when ng-repeat runs again, your previously 2nd object which was tagged as "1", is now your 1st and only object which is tagged as "0", since the $index is evaluated again starting from 0.

Angular believes that the 1st DOM element still points to the same object cause it finds it tagged as "0", regardless that it's a completly different object. Under the hood, $scope has the correct model values but ng-repeat skips re-rendering of the DOM element.

It very difficult to write down what really happens. Hope I got it right and helped you.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM