AngularJS : custom iterations/data transformations

2020-06-29 05:54发布

问题:

Still this problem Angular.js more complex conditional loops but I felt that the answer to the question as it was asked was right so I accepted it.

So let me elaborate more than I did in the original question.

I'm trying to get this

<h3>11.4.2013</h3>
<ul>
 <li>oofrab | 4 | 11.4.2013 14:55 <button>remove</button></li>
 <li>raboof | 3 | 11.4.2013 13:35 <button>remove</button></li>
</ul>

<h3>10.4.2013</h3>
<ul>
 <li>barfoo | 2 | 10.4.2013 18:10 <button>remove</button></li>
 <li>foobar | 1 | 10.4.2013 12:55 <button>remove</button></li> 
</ul>

from this data structure

[
    {
        "id": 4,
        "name": "oofrab",
        "date": "2013-11-04 14:55:00"
    },
    {
        "id": 3,
        "name": "raboof",
        "date": "2013-11-04 13:55:00"
    },
    {
        "id": 2,
        "name": "barfoo",
        "date": "2013-10-04 18:10:00"
    },
    {
        "id": 1,
        "name": "foobar",
        "date": "2013-10-04 12:55:00"
    }
]

Basically the only extra thing over the standard ng-repeat I want to add are those headings. And I simply can't believe I'd have to go thru so many problems by adding them.

This is what I ended up with using the answer I got in the first question http://plnkr.co/edit/Zl5EcsiXXV92d3VH9Hqk?p=preview

Note that there can realistically be up to 400 entries. And I need to be able to add/remove/edit entries on the fly

What the example on plunker is doing is this:

iterating thru the original data creating a new data structure looking like this

{
  "2013-10-05": [
    {
      "id": 4,
      "name": "oofrab",
      "date": "2013-10-05 14:55:00",
      "_orig_index": 0
    },
    {
      "id": 3,
      "name": "raboof",
      "date": "2013-10-05 13:55:00",
      "_orig_index": 1
    }
  ],
  "2013-10-04": [
    {
      "id": 2,
      "name": "barfoo",
      "date": "2013-10-04 18:10:00",
      "_orig_index": 2
    },
    {
      "id": 1,
      "name": "foobar",
      "date": "2013-10-04 12:55:00",
      "_orig_index": 3
    }
  ]
}

allowing me to then get the result I wanted by doing this

<div ng-repeat="(date,subItems) in itemDateMap">
<h3>{{date}}</h3>
<ul>
  <li ng-repeat="item in subItems">
    {{item.name}} | {{item.id}} | {{item.date}}
    <button type="button" ng-click="removeItem(item._orig_index)">x</button>
  </li>
</ul>  
</div>

Great. But it comes with a cost of shizzload of problems. Everytime a new item is added I have to rebuild the itemDateMap, everytime an item is deleted I have to rebuild the itemDateMap, everytime date is changed, I have to rebuild the itemDateMap. When I want to remove an item, I have to first get index of its original reference. And everytime itemDateMap is rebuilt, the whole thing is re-rendered. And it can't be sorted, as it's an object rather than an array.

When there's a couple of hundred of entries, it also becomes really, really slow. I read somewhere that ng-repeat is quite intelligent, watching values, moving nods in dom rather than re-rendering everything and stuff, but it surely doesn't work this way when I rebuild the whole structure.

This can't be right, all this hassle to do a very, very simple thing..

What should I do?

回答1:

This is my suggestion - just work with one structure, and only expose one structure to the scope (the map). And create a function to add an array of items to the map, and a function that transforms the map into an array (I assume you need this array for server communication or something).

  var toKey=function(item){
    return moment(item.date).format("YYYY-MM-DD");
  }

  $scope.itemDateMap = {};
  $scope.addItemToDateMap=function(item){
    var key = toKey(item);
    if(!$scope.itemDateMap[key]){
      $scope.itemDateMap[key] = [];
    }
    $scope.itemDateMap[key].push(item);    
  }

  $scope.removeItemFromDateMap=function(item){
    var key = toKey(item), subitems = $scope.itemDateMap[key];
    var index = subitems.indexOf(item);
    subitems.splice(index,1);
    if(subitems.length === 0){
      delete $scope.itemDateMap[key];
    }
  }

  var addArrayToMap = function(items){
    for(var i=0; i<items.length; i++){
      var item = items[i]; 
      $scope.addItemToDateMap(item);
    }
  };

  $scope.mapToArray = function(){
    var items = [];
    for(var key in $scope.itemDateMap){
      var subitems = $scope.itemDateMap[key];
      for(var j=0;j<subitems.length;j++){
        var item = subitems[j];
        items.push(item);
      }
    }
    return items;
  }

I've updated your plnkr with my suggestion. I think it performs quite well.

Oh - I just noticed you want it sorted - I don't have time to update my example, but it is not very complicated. Use this structure instead (array with objects with arrays, instead of object with array) - this way you can use the orderBy:'date' on the root array:

[
{
  date:"2013-10-05",
  items: [
    {
      "id": 4,
      "name": "oofrab",
      "date": "2013-10-05 14:55:00"
    },
    {
      "id": 3,
      "name": "raboof",
      "date": "2013-10-05 13:55:00"
    }
  ]
},
{
date:"2013-10-04",
items: [
    {
      "id": 2,
      "name": "barfoo",
      "date": "2013-10-04 18:10:00"
    },
    {
      "id": 1,
      "name": "foobar",
      "date": "2013-10-04 12:55:00"
    }
  ]
}
]