Search code examples
javascriptjquerymodelangularjstoggleclass

AngularJS lost css class during model update (DOM re-draw)


I've got a table whose rows loop thru some model data. Onclick of a table cell, all the cells of that row toggle .active (background becomes red). That works fine (although I'll eventually add a more sophisticated checker that removes .active from all of the row's siblings). However, my table is receiving data via WebSocket pushes/messages that update the model and cause the table to be re-drawn. When this happens, my "selection" is lost.

<tr ng-repeat="item in items | orderBy:'time':true">
  <td
    ng-click="items[$index].active =! items[$index].active"
    ui-refresh="items[$index].active"
    ui-jq="toggleClass"
    ui-options="'active'"
  >{{item.time | date:'hh:mm a'}}</td>
  <td
    ng-click="items[$index].active =! items[$index].active"
    ui-refresh="items[$index].active"
    ui-jq="toggleClass"
    ui-options="'active'"
  >
    <a href="#/items/details/{{item.id}}">{{item.name}}</a>
  </td>
</tr>

In my controller, the new WebSocket message is pushed my array items:

function Items($scope , WebSocket)
{
  $scope.items = [];

  WebSocket.on('items', function(data)
  {// receive a bunch of items onload
    $scope.items = data.items;
  });

  WebSocket.on('item', function(data)
  {// receive subsequent items as they are pushed
    $scope.items.push(data.item);
  });
}

So I'm wondering why items[$index].active does not persist thru the DOM re-draw (and retain its active css class). Also if anyone has a solution as to how I can persist my "state", that would be great.

Usually I loose the css class altogether, but sometimes (seemingly randomly) it re-appears on a new row in the same position of the table (say 2nd row) as the original row, even though the original row has been shifted down (to what is then the 5th row) and the new row that active appears on didn't exist when active was added.

I would post an example, but neither Plunkr nor jsfiddle support NodeJS (which pushes out the WebSocket messages) and is necessary to demonstrate the behaviour.

Solution

Based on Alex's answer, I modified my controller slightly to add a second array:

function Items($scope , WebSocket)
{
  $scope.items = [];
  $scope.rowSelection = [];// <- THIS IS NEW

  WebSocket.on('items', function(data)
  {// receive a bunch of items onload
    $scope.items = data.items;
  });

  WebSocket.on('item', function(data)
  {// receive subsequent items as they are pushed
    $scope.items.push(data.item);
  });
}

And in my template, swapped items[$index].active for rowSelection[$index] (since the sole purpose of this second array is to remember who is selected/active, it doesn't need an active property).


Solution

  • You are losing the values in items[$index].active because the whole items array is being overwritten when the WebSocket updates.

    To keep the selected status, you should store this in a separate variable that is not affected.

    Something like:

    In controller

    $scope.items = [];
    $scope.itemStatus = [];
    

    In directive/template

      <td
        ng-click="itemStatus[item.id].active =! itemStatus[item.id].active"
        ui-refresh="itemStatus[item.id].active"