ng-if being called more times than it should
Asked Answered
I

1

5

I'm trying to filter some channels depending on the selected category.

I have an ng-repeat of channel in channels which has an ng-if="hasCategory(channel.category)

Here's the document:

db.channels.find().pretty()
{
    "_id" : "2kj9GK8jE9yHezoWb",
    "name" : "example name",
    "logo" : "path/to/image.svg",
    "number" : 2,
    "category" : [
        "Broadcast",
        "Premium"
    ],
    "tags" : "example tag tags"
}

This is the function to handle the ng-if:

$scope.hasCategory = function (categoryNameArray) {
  console.log('thisisbeingcalled');
  var e = _.indexOf(categoryNameArray, $scope.activeCategory);
  if (e === -1) {
    return false;
  } else {
    return true;
  }
};

and this is how I set the active category:

$scope.setCategory = function (name) {
  $scope.activeCategory = name;
};

which I send by clicking the button in the view:

<section layout="row" layout-align="center center" layout-wrap ng-init="activeIndex; activeCategory">
  <md-button flex="auto" flex-sm="45" flex-xs="100" ng-repeat="kit in kits | orderBy: 'order'" ng-class="{active: (activeIndex.index == $index)}" class="md-raised">
    <a href="" ng-click="activeIndex.index = $index; setCategory(kit.name);" class="bold">{{kit.name}}</a>
  </md-button>
</section>

here's the kits document schema:

db.kits.find().pretty()
{ "_id" : "k7JFX6DHu5GYnyer3", "name" : "Broadcast", "order" : 1 }
{ "_id" : "KrKRm4gysw5sfQnnr", "name" : "Premium", "order" : 2 }
{ "_id" : "dieukQ3NRsA44CSns", "name" : "Ultimate", "order" : 3 }

Now I only have one channel to test this out, but each time I click the button to change the category, the $scope.hasCategory function gets called multiple times even at the startup of the page (first time loading)

enter image description here

I also have a search bar to search for channels in the current category, I'm not sure if that affects this as well.

<input type="text" ng-model="queryname" ng-model-options="{debounce: 500}">

Now this gets really laggy when I have more than a 100 channels, the ng-if gets called more than 10k times, which makes the page freeze for quite a while.

What can I do to fix this issue?

EDIT

Forgot to add where the ng-if was:

<md-card flex="15" flex-xs="40" class="slide-up" layout="column" ng-repeat="channel in channels | orderBy: 'number' | filter: queryname" ng-if="hasCategory(channel.category)" ng-init="channel.edit = false">
  <md-card-header ng-show="channel.edit == false">
    <img ng-src="{{channel.logo}}" alt="">
  </md-card-header>
  <md-card-header-text ng-show="channel.edit == false">
    <span class="md-subhead dark-blue" layout="row" layout-align="center" layout-margin>{{channel.number}}</span>
  </md-card-header-text>
</md-card>
Imitate answered 27/1, 2016 at 14:33 Comment(0)
P
8

Your ng-if will be called on every digest loop. The problem is that it contains an expression involving a function call. Angular has no way to know when the result of the expression might change, so it calls it every time.

A better option would be to set a flag in each of the channels and use ng-if to just test the relevant flag. Then you just have to update the flags whenever $scope.activeCategory changes which you can either do with code where you set the category or using $scope.$watch() to trigger it automatically.

e.g.

$scope.setCategory = function (name) {
  $scope.activeCategory = name;
  for (var index=0; index < $scope.channels.length; index++) {
      $scope.channels[index].hasCategory = hasCategory($scope.channels[index].category, name);
  }
};

function hasCategory(categoryNameArray, name) {
  console.log('thisisbeingcalled');
  var e = _.indexOf(categoryNameArray, name);
  if (e === -1) {
    return false;
  } else {
    return true;
  }
}

Then in your template:

<md-card flex="15" flex-xs="40" class="slide-up" layout="column"
    ng-repeat="channel in channels | orderBy: 'number' | filter: queryname"
    ng-if="channel.hasCategory"
    ng-init="channel.edit = false">
  <md-card-header ng-show="channel.edit == false">
    <img ng-src="{{channel.logo}}" alt="">
  </md-card-header>
  <md-card-header-text ng-show="channel.edit == false">
    <span class="md-subhead dark-blue" layout="row" layout-align="center" layout-margin>{{channel.number}}</span>
  </md-card-header-text>
</md-card>

should be more efficient.

Plush answered 27/1, 2016 at 14:59 Comment(5)
ahh, thank you so much! I did some tweaks to your code like $scope.channels[index].hasCategory = hasCategory($scope.channels[index].category, name); and now it's working, another question is, how to set the starting category so the user doesn't have to select a category in order to view the channels.Imitate
in your method hasCategory(). You should change your return value to return !(e === -1) . It's more readableTolerant
@Riverside, agreed that would be an improvement, but when I answer questions I try to keep code changes from the original to the minimum otherwise there's a risk of obscuring the changes actually required for the answer.Plush
@Plush You're right, and it's totally make sense. This is just a little improvement like you said.Tolerant
Would something like ng-if = "$ctrl.categories.includes(channel)" also be called on every digest loop? Where categories is a list of strings and channel a string.Tophet

© 2022 - 2024 — McMap. All rights reserved.