Strife86
Strife86

Reputation: 1155

Improve performance ng-repeat list 750 elements

wanna know the best way to improve performance in my scenario a list of 750 elements filtered in real time and with the requirement of no pagination or limit the list, the 750 elements should be on screen and need to be filtered as fast as posible in real time with a input text, obviously the performance using

ng-repeat="x in collection | filter: x" 

is not quite good here, any ideas?

EDIT: Current Scenario

 <section ng-repeat="(key, value) in content">
    <md-subheader class="md-primary">{{ key == '1' ? '#' : key }}</md-subheader>
    <md-list layout-padding>
        <md-list-item class="md-3-line" ng-repeat="person in value" layout="row">
            <div flex="15" flex-md="10" layout="column" layout-align="center center">
                <span>{{ ::person.id_lista_nominal }}</span>
            </div>
            <div class="md-list-item-text" flex>
                <h3>{{ ::person.nombre_completo }}</h3>
                <h4>{{ ::person.ine }}</h4>
            </div>
            <div flex="15" flex-md="10" layout="column" layout-align="center center">
                <md-button aria-label="Votó" class="md-primary md-raised text-white" ng-click="confirm_voting(person, $event)"><md-icon md-font-icon="mdi-navigation-check icon-md"></md-icon> </md-button>
            </div>
        </md-list-item>
    </md-list>
</section>

Now all the filtering is in the controller for better performance, in this function

        var update_list_content = function () {
            //filter implementation in controller

            var tmp = angular.copy(original);
            tmp = $filter('filterBy')(tmp, ['nombre_completo', 'nombre_inverso'], $scope.param.search);
            tmp = $filter('orderBy')(tmp, 'nombre_completo');
            tmp = $filter('limitTo')(tmp, $scope.config.limit);
            tmp = $filter('groupBy')(tmp, 'grupo');

            $scope.content = tmp;
        };

As it was correctly pointet out I havent been very clear with my goal here, so here it is:

Im developing a mobile app using cordova, all of this runs fine and fluid on pc but it feels laggy on mobile

in this I have a list like a contact list, the first ng-repeat reorder the list and split the list into groups (the sub-header isthe group name)

the nested ng-repeat is used to display the list of persons inside each group

the procees of this screen is quite simple and because of that a laggy interface is not acceptable

the user only searches in the list by scrolling it or by filtering by person name, the button on every person will ask if the user wats to "mark" the contact to proccess, if the user agrees the process will execute a simple websql command to set mark = 1 to the selected person by its id and it will remove that person from the list.

but the filtering with the onscreen 750 persons in the list feels buggy and every time the user marks a person, it takes like 3 or 4 seconds to update the list and show the removal of the marked

the desired experience is a fluid scroll, a fast search and an inmediate removal after mark, I know if I limit the number of persons onscreen (lets say to 50) I gain a lot of performance in the proccess but sadly for me thats not an option unless perhaps manage a way to simulate they can scroll up and down but limiting the amount of displayed data :S

Upvotes: 0

Views: 2136

Answers (5)

N.K
N.K

Reputation: 1690

You don't have to limit the numbers of elements itself, but you can load them on scroll event if you detect it.

For exemple here I will admit that you use a common mysql database where all your data is stored, I use two variables to do this and get all my users commentary, it seems like that (read the comments):

    //Function to get all comments progressively
    $scope.getAllCommentsByUserId = function(){

        //action name, here actionVar's value will be my php case, if 
        //$_POST['action'] equal this value then i will send my sql request

        var actionVar = "getAllCommentsByUserId";

        //Define callback function
        var callback = function(resp){

            if(resp.success == 1){

                // $scope.userComments will be my array were the sql results
                // from resp will be stored, also it's the main varaible of 
                // my ng-repeat loop, i push them because i don't want 
                // to erase the comments i already requested, so the new ones  
                // will be at the end of the array

                angular.forEach(resp.comments, function(comment, key) {
                    $scope.userComments.push(comment);

                });

            // In case i got an error istead of a good result

            }else{
                $scope.$emit('showAlert', ["Une erreur est survenue","error"]);

            }

        };

        // In case you want to know, userId is not needed, i use it just to 
        // request all the comments on the current user's page. that's just logic.

        var params = {
            'action' : actionVar,
            'idUser' : $scope.bigouder.idUser,
            'begin' : $scope.beginComments,
            'length' : $scope.lengthComments
        };

        //HTTP REQUEST
        $http.post(url, params)
        .success(callback)
        .error(function(){
            $scope.$emit('showAlert', ["ERROR","error"]);

        });
    };

   // I update the begining element index by adding the length to it
   // YOU HAVE TO SET "beginComments" AND "lengthComments" AT THE BEGINING OF 
   // YOUR JS FILE, to give them a default value, otherwise that will never work.

   $scope.changeMaxComments = function(){
        $scope.beginComments += $scope.lengthComments;
        $scope.lengthComments = 20;
        $scope.getAllCommentsByUserId();
    };

Basically what i do here is that i set 2 values:

  • beginComments and lengthComments

beginComments will be the element from where my sql request will start saving the finded results, lengthComments will be the number of results i will save.

The sql request will be something like that: $query = "select ... from ... where ... order by ... limit". $_POST['begin'] . "," $_POST['length'];

so i use my two variable with the limit parameter in my sql request, i will take all the results from the "beginComments" element to the "beginComments + lengthComments" element.

You just have to call something like that when you detect a scroll event, angular will do the job and refresh your ng-repeat. :)

Upvotes: 1

Strife86
Strife86

Reputation: 1155

I think i found the solution to my own problem googling various types of workarounds i found this

Angular Virtual Scroll

An awesome directive that manage the concept of virtual scroll directly on ng-repeat loops of angular, that way the view only renders a few items while its scrolling it loads the rest but unlike lazy loading the user dont see the scroll bar incresing while scrolling down cause it alreade have the space calculated and it removes elements you pass scroll outside the view port so, you'll always have the 'illusion' of having the full set of items only rendering a few

Upvotes: 0

Michael J. Calkins
Michael J. Calkins

Reputation: 32635

  1. Simplify your filter somehow so that your controller or service is generating the items that should be listed instead of doing that in the view. (key, value) in filteredContent()
  2. Another idea would be to use https://github.com/Pasvaz/bindonce and updated the data on an event if possible.
  3. Just keep your watch count below 1,200 (when I experienced slowness) and you'll be fine. http://www.bennadel.com/blog/2698-counting-the-number-of-watchers-in-angularjs.htm

Otherwise I'd google lowering watch counts and writing effective filters for AngularJS.

Upvotes: 0

You don't explain your requirements... AngularJS can be very heavy for large amounts of data, but 750 simple items shouldn't be a problem. You have several alternatives:

  • Use ReactJS (here a guide) for faster rendering, just delegate that view to reactJS.

  • Change your filter input to have a delay so it doesn't change the model on every key pressed. First instead of listen for param.search on your filter, listen to other variable searchText.

For example:

 $scope.$watch('param.search', function (newValue) {
      if (searchTextTimeout) {
        $timeout.cancel(searchTextTimeout);
      }
      searchTextTimeout= $timeout(function() {
        $scope.searchText= newValue;
      }, 300); // between 200-300 ms should be a good user experience
  });

You can also apply React and delay the filter. If still your performance is poor, probably the issue is in your styles, a single style could causes a lot of repaints, for instance if you are animating the position change when filter, that would be very slowly to animate.

I hope this works for you.

Update

Also i noticed that you are using this directive md-subheader which only seems to add a #1 or key what you need to show? if is the numeration you can use $index instead

Upvotes: 0

Abdullahi
Abdullahi

Reputation: 144

Limit the amount of elements that show initially then show more elements as the user scrolls. Like tweeter infinite scrolling (When a user gets to the bottom of the page it loads more tweets)

Upvotes: 0

Related Questions