Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

ui-select multiselect is very slow in displaying the choices

I ran into this problem, and I don't know how to solve it. I have used a ui-select multiselect in my page. First, a http.get request is made to a url which gets the data, then the ui-select choices are populated. The data is big - the length of the data is 2100. This data is to be shown as choices. (The data is fetched at the beginning during the loading of the page and is stored in an array)

But the problem is each time I click on the multiselect to select a choice, it takes 4-5 seconds to populate the list and the page becomes very slow. What do I do to reduce this time?

The choices data is stored in an array, the datatype is array of strings.

  <ui-select multiple ng-model="selectedFields.name"  style="width: 100%;">
    <ui-select-match placeholder="Select fields...">{{$item}}</ui-select-match>
    <ui-select-choices repeat="fields in availableFields | filter:$select.search">
      {{fields}}
    </ui-select-choices>
  </ui-select>

in the controller,

$scope.selectedFields = {};
$scope.selectedFields.name = [];

$scope.init = function() {

    $http.get(url)
        .success( function(response, status, headers, config) {
            availableFields = response;
        })
        .error( function(err) {
        });
};

$scope.init();

If not this way, is there any other options/choice I can work with which doesn't delay showing the select-choices?

like image 537
akashrajkn Avatar asked Jul 14 '15 11:07

akashrajkn


2 Answers

This is a known issue in ui-select. I tried the following ways, both work

1) There is a workaround for this - use

| limitTo: 100

This limits the choice display to 100 but all the choices can be selected. Look at this thread for more details.

2) Since some of the time, there is a need to display the entire list in the choices, 1) is not a viable option. I used a different library - selectize.js. Here's a plunker demo given in the page

like image 86
akashrajkn Avatar answered Nov 12 '22 19:11

akashrajkn


Here is complete solution that decorates uiSelectChoices directive.

Items are populated progressively as the user scrolls.

Also takes care of searches in the scrolls.

Also works for all values of position={auto, up, down}

Example

    <ui-select-choices 
         position="up" 
         all-choices="ctrl.allTenThousandItems"  
         refresh-delay="0"
         repeat="person in $select.pageOptions.people | propsFilter: {name: $select.search, age: $select.search} ">
      <div ng-bind-html="person.name | highlight: $select.search"></div>
      <small>
         email: {{person.email}}
         age: <span ng-bind-html="''+person.age | highlight: $select.search"></span>
       </small>
   </ui-select-choices>

Working Plnkr Also with With v0.19.5

The directive

  app.directive('uiSelectChoices', ['$timeout', '$parse', '$compile', '$document', '$filter', function($timeout, $parse, $compile, $document, $filter) {
    return function(scope, elm, attr) {
    var raw = elm[0];
    var scrollCompleted = true;
    if (!attr.allChoices) {
      throw new Error('ief:ui-select: Attribute all-choices is required in  ui-select-choices so that we can handle  pagination.');
    }

    scope.pagingOptions = {
      allOptions: scope.$eval(attr.allChoices)
    };

    attr.refresh = 'addMoreItems()';
    var refreshCallBack = $parse(attr.refresh);
    elm.bind('scroll', function(event) {
      var remainingHeight = raw.offsetHeight - raw.scrollHeight;
      var scrollTop = raw.scrollTop;
      var percent = Math.abs((scrollTop / remainingHeight) * 100);

      if (percent >= 80) {
        if (scrollCompleted) {
          scrollCompleted = false;
          event.preventDefault();
          event.stopPropagation();
          var callback = function() {
            scope.addingMore = true;
            refreshCallBack(scope, {
              $event: event
            });
            scrollCompleted = true;

          };
          $timeout(callback, 100);
        }
      }
    });

    var closeDestroyer = scope.$on('uis:close', function() {
      var pagingOptions = scope.$select.pagingOptions || {};
      pagingOptions.filteredItems = undefined;
      pagingOptions.page = 0;
    });

    scope.addMoreItems = function(doneCalBack) {
      console.log('new addMoreItems');
      var $select = scope.$select;
      var allItems = scope.pagingOptions.allOptions;
      var moreItems = [];
      var itemsThreshold = 100;
      var search = $select.search;

      var pagingOptions = $select.pagingOptions = $select.pagingOptions || {
        page: 0,
        pageSize: 20,
        items: $select.items
      };

      if (pagingOptions.page === 0) {
        pagingOptions.items.length = 0;
      }
      if (!pagingOptions.originalAllItems) {
        pagingOptions.originalAllItems = scope.pagingOptions.allOptions;
      }
      console.log('search term=' + search);
      console.log('prev search term=' + pagingOptions.prevSearch);
      var searchDidNotChange = search && pagingOptions.prevSearch && search == pagingOptions.prevSearch;
      console.log('isSearchChanged=' + searchDidNotChange);
      if (pagingOptions.filteredItems && searchDidNotChange) {
        allItems = pagingOptions.filteredItems;
      }
      pagingOptions.prevSearch = search;
      if (search && search.length > 0 && pagingOptions.items.length < allItems.length && !searchDidNotChange) {
        //search


        if (!pagingOptions.filteredItems) {
          //console.log('previous ' + pagingOptions.filteredItems);
        }

        pagingOptions.filteredItems = undefined;
        moreItems = $filter('filter')(pagingOptions.originalAllItems, search);
        //if filtered items are too many scrolling should occur for filtered items
        if (moreItems.length > itemsThreshold) {
          if (!pagingOptions.filteredItems) {
            pagingOptions.page = 0;
            pagingOptions.items.length = 0;
          } else {

          }
          pagingOptions.page = 0;
          pagingOptions.items.length = 0;
          allItems = pagingOptions.filteredItems = moreItems;

        } else {
          allItems = moreItems;
          pagingOptions.items.length = 0;
          pagingOptions.filteredItems = undefined;
        }


      } else {
        console.log('plain paging');
      }
      pagingOptions.page++;
      if (pagingOptions.page * pagingOptions.pageSize < allItems.length) {
        moreItems = allItems.slice(pagingOptions.items.length, pagingOptions.page * pagingOptions.pageSize);
      }

      for (var k = 0; k < moreItems.length; k++) {
        pagingOptions.items.push(moreItems[k]);
      }

      scope.calculateDropdownPos();
      scope.$broadcast('uis:refresh');
      if (doneCalBack) doneCalBack();
    };
    scope.$on('$destroy', function() {
      elm.off('scroll');
      closeDestroyer();
    });
  };
}]);
like image 8
bhantol Avatar answered Nov 12 '22 19:11

bhantol