1

I am looking for a little bit of help with logically fitting two objects with common reference into AngularJs ngRepeat.

Example objects (these get called from a service):

$scope.objArr1 = [
    { id: 1, Name: 'Name 1', Value: 'Value 1', },
    { id: 2, Name: 'Name 3', Value: 'Value 2', },
    { id: 3, Name: 'Name 3', Value: 'Value 3', },
];

$scope.objArr2 = [
    { id: 1, Name: 'Name 1', Value: 'Value 1', ObjArr1: { id: 1, Name: 'Name 1', Value: 'Value 1', }, },
    { id: 2, Name: 'Name 1', Value: 'Value 1', ObjArr1: { id: 1, Name: 'Name 1', Value: 'Value 1', }, },
    { id: 3, Name: 'Name 1', Value: 'Value 1', ObjArr1: { id: 3, Name: 'Name 3', Value: 'Value 3', }, },
];

Something along those lines. Basically if you can think of it this way; first array objects form buckets while second array objects form items that fit into corresponding bucket.

First approach

HTML:

<ul>
    <li data-ng-repeat="item in objArr1 | filter : someFilter">{{item.Name}}
        <ul>
            <!-- how to filter objArr2 items based on objArr1 property ? -->
            <li data-ng-repeat="item2 in objArr2 | filter : someOtherFilter">{{item2.Name}}</li>
        </ul>
    </li>
</ul>

In simple terms I was trying to filter $scope.objArr2 items that correspond to the current repeater item in the inner repeater. I tried various things with someOtherFilter but I was unable to reference the item from outer repeater.

Problem

I couldn't figure out how get this filtering bit to work.

Second approach

When all else failed I decided to combine the data structures into one like so:

// deep copy to avoid dependency
angular.copy($scope.objArr1, $scope.objArr3);
// loop over objArr3 and add empty array objArr2
// which we will populate a bit later
angular.forEach($scope.objArr3, function (val, key) {
    $scope.objArr3[key]["objArr2"] = [];
});

Then I setup a $watch-er`to monitor both objArr1 and objArr2 because I don't know when these will return.

$scope.$watchGroup(['objArr1', 'objArr2'], function (newVals, oldVals) {
    // check to make sure there is stuff to loop over
    // i am wrongly assuming there will be items in both objArr1 and objArr2
    // i'll worry about what to do when there is no data a bit later
    if(newVals[0].length > 0 && newVals[1].length > 0) {
        angular.forEach($scope.objArr1, function (val1, key1) {
            angular.forEach($scope.objArr2, function (val2, key2) {
                 if (val1.Id === val2.objArr1.Id) {
                     $scope.objArr3[key1].objArr2.push(val2);
                 }
            });
        });
    }
});

HTML:

<ul>
    <li data-ng-repeat="item in objArr1 | filter : someFilter">{{item.Name}}
        <ul>
            <li data-ng-repeat="item2 in item.objArr2">{{item2.Name}}</li>
        </ul>
    </li>
</ul>

Problem

While this has worked just fine on the surface I get a lovely Error: [$rootScope:infdig] 10 $digest() iterations reached. Aborting! in the console.

I am a bit puzzled what would cause for $digest to fire so many times.

However, by commenting update line $scope.objArr3[key1].objArr2.push(val2); of my watcher the error goes away. But then I don't understand how this would result in extra digest iterations.

Halp

In the end either of the approach that I came up with has some problem. While second approach actually does its job and populates my repeater correctly but there is that nasty error in the console.

Anyone with a bit more experience in this field please help.

Update

Some of the silly things I tried with someOtheFilter are:

data-ng-repeat="item2 in objArr2 | filter : someOtherFilter"
$scope.someOtherFilter = function(item){
    // item is always the current inner repeaters item2 object
    // that just the way angular filter works
    return item.objArr2 === $scope.objArr1.Id; // this is silly but idea is there
};

data-ng-repeat="item2 in objArr2 | filter : someOtherFilter(item)"
$scope.someOtherFilter = function(item){
    // if memory serves me right
    // in this case item is always repeaters current item2 object
    // with no ability to reference outer repeaters current item object
}

data-ng-repeat="item2 in objArr2 | filter : someOtherFilter(item, item2)"
$scope.someOtherFilter = function(item, item2) {
    // if memory serves me right
    // in this case item was always inner repeaters current item2 object
    // and item2 is always undefined
    // again with no ability to reference outer repeaters current item
}

At this point I gave up on first approach. But thinking about it now I might have been able to utilise $index (if inner repeater somehow or other didn't overwrite outer repeaters $index reference) to get index value of the outer repeater and try to get at $scope.objArr1[index].

No matter which scenario would have worked for someOtherFilter inner working only need to compare inner object objArr1.Id to outer objects Id.

UPDATE (learn from my mistakes)

OK, after confirming the answer as working I still had the same issue in my production example Error: [$rootScope:infdig] 10 $digest() iterations reached. Aborting!.

After cooling down for a few days I decided to revisit this problem and this is what I found.

<div class="block" data-ng-repeat="team in Teams | filter : validateAgeCategory">
    <div data-ng-style="getHeaderStyle()">
        <span>{{team.Name}}</span>
        <!-- bunch of things removed for brevity -->
    </div>
    <ul data-ng-style="getListStyle()">
        <li data-ng-repeat="players in Players | filter : { Team: { Id: team.Id, }, }">
            <a data-ng-style="getListItemStyle()" data-ng-href="#/players/{{player.Id}}">{{player.Name}}</a>
        </li>
    </ul>
</div>

I adapted my Team/Player example for easier understanding. Regardless notice that in production I use a few ng-style calls to retrieve CSS.

Reason why I am doing so is because IE has a tendency to remove {{}} definitions from inline style="color: {{color}};" definition during document load. It's IE bug so to speak.

Moving on, what I found is that the innermost ng-style was causing the error with $digest. By removing data-ng-style="getListItemStyle()" everything is happy. Everything bu me of course.

Looking at this as an overhead it would be better to create CSS classes and instead apply classes based on some indexing to style my HTML.

There you have it.

iiminov
  • 969
  • 1
  • 15
  • 34
  • can you give an example to what you tried in the first approach in `someOtherFilter`? – Yaron Schwimmer Jul 08 '15 at 11:42
  • @yarons certainly. Please have a look at my pathetic attempts with ``someOtehrFilter``. – iiminov Jul 08 '15 at 12:19
  • but what exactly are you trying to achieve? what do you want to filter? – Yaron Schwimmer Jul 08 '15 at 12:26
  • Sorry @yarons as per my example I was trying to group ``objArr2`` objects under corresponding ``objArr1`` objects. My first approach was to use a filter which I though was the easiest but it turned out otherwise. With my second approach I did exactly what I said. I grouped ``objArr2`` under corresponding ``objArr1`` object and assigned combined data as ``objArr3``. While I succeeded in removing the need for second repeater I got stuck with this ``$digest`` error. – iiminov Jul 08 '15 at 12:50
  • Its all to do with ``HTML`` realy. I tried to group both datasets so that it would make sense to the user. – iiminov Jul 08 '15 at 12:55

1 Answers1

1

OK, I'll try my best to help.

I think the problem with your second approach is somehow related to this question. Read the comments there. It might be related to the list being changed by the filter.

As for your first approach, I'm still not sure what you were trying to do, but I've created this example to show you that you can filter inside nested ngRepeats.

BTW, If you need to access outer $index inside an inner ngRepeat, you can use ngInit.

Community
  • 1
  • 1
Yaron Schwimmer
  • 5,327
  • 5
  • 36
  • 59
  • that is very nicely done I must say. I noticed what was throwing you off (my bad). ``filterSomething`` was actually used to filter based another property on ``objArr1`` which I omitted. So, ``filterSomething`` should have not been there. I've update the example you put together with what I was trying to achieve with first aproach - http://plnkr.co/edit/0PAazrF6DZftik4RJX05?p=preview. – iiminov Jul 08 '15 at 13:57
  • you asked "how to filter objArr2 items based on objArr1 property". can you describe schematically how you want the output to look like? – Yaron Schwimmer Jul 08 '15 at 14:03
  • To make it easier to understand I changed object names to Teams and Players. – iiminov Jul 08 '15 at 14:03
  • Let's say I had a age group property on each team. The ``filterSomething`` would be used to filter by that age category. That is all really. – iiminov Jul 08 '15 at 14:06
  • Hrm, it looks like the same error surfaced with this approach. It looks like my dataset might just be too convoluted with inner and outer filters. But in any case it works. So time to dig into that other thread. – iiminov Jul 08 '15 at 14:52
  • OK, I have found culprit that caused the error with $digest. Please refer to the Update in original question (and don't do it like I did). – iiminov Jul 10 '15 at 12:53