web-dev-qa-db-fra.com

ui-select multiselect est très lent à afficher les choix

J'ai rencontré ce problème et je ne sais pas comment le résoudre. J'ai utilisé un i-select multiselect dans ma page. Tout d'abord, une demande http.get est envoyée à une URL qui obtient les données, puis les choix ui-select sont remplis. Les données sont volumineuses - la longueur des données est de 2100. Ces données doivent être affichées comme choix. (Les données sont récupérées au début lors du chargement de la page et sont stockées dans un tableau)

Mais le problème est qu'à chaque fois que je clique sur la multisélection pour sélectionner un choix, il faut 4 à 5 secondes pour remplir la liste et la page devient très lente. Que dois-je faire pour réduire ce temps?

Les données de choix sont stockées dans un tableau, le type de données est un tableau de chaînes.

  <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>

dans le contrôleur,

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

$scope.init = function() {

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

$scope.init();

Sinon, y a-t-il d'autres options/choix avec lesquels je peux travailler qui ne tardent pas à afficher les choix sélectionnés?

19
akashrajkn

Il s'agit d'un problème connu dans ui-select. J'ai essayé les méthodes suivantes, les deux fonctionnent

1) Il existe une solution à ce problème - utilisez

| limitTo: 100

Cela limite l'affichage des choix à 100 mais tous les choix peuvent être sélectionnés. Regardez ce fil pour plus de détails.

2) Depuis un certain temps, il est nécessaire d'afficher la liste entière dans les choix, 1) n'est pas une option viable. J'ai utilisé une bibliothèque différente - selectize.js . Voici une démo plunker donnée dans la page

15
akashrajkn

Voici une solution complète qui décore la directive uiSelectChoices.

Les éléments sont remplis progressivement au fur et à mesure que l'utilisateur défile.

Prend également en charge les recherches dans les parchemins.

Fonctionne également pour toutes les valeurs de position={auto, up, down}

Exemple

    <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 Aussi avec Avec v0.19.5

La 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();
    });
  };
}]);
7
bhantol

Parce que je ne peux pas laisser de commentaire (pas assez de représentants), j'écris ceci comme une réponse et je suis désolé que ce ne soit pas une réponse au problème.

@bhantol J'ai changé la ligne de code suivante pour votre solution qui fonctionne parfaitement pour moi jusqu'à présent

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

for (var k = 0; k < moreItems.length; k++) {
  if (pagingOptions.items.indexOf(moreItems[k]) == -1){
    pagingOptions.items.Push(moreItems[k]);
  }
}

Cela empêche les éléments en double de s'afficher si l'utilisateur commence à écrire un filtre, puis le supprime.

De plus, je viens de comprendre que si la liste est inférieure à 20 éléments, cela ne fonctionnera pas, j'ai donc changé:

if (pagingOptions.page * pagingOptions.pageSize < allItems.length) {
  moreItems = allItems.slice(pagingOptions.items.length, pagingOptions.page * pagingOptions.pageSize);
}

à:

if (pagingOptions.page * pagingOptions.pageSize < allItems.length) {
  moreItems = allItems.slice(pagingOptions.items.length, pagingOptions.page * pagingOptions.pageSize);
}
else{ moreItems = allItems;}

Peut-être que cela vous aidera d'une manière ou d'une autre et désolé de ne pas avoir répondu à la question.

4
d3orn

Comme indiqué, ui-select rencontre un certain nombre de problèmes de performances, mais il existe une solution de contournement pour le problème de limite.

Si vous suivez l'approche d'Akashrajkn, vous remarquerez qu'il supprimera en fait des éléments de données importants car il ne rendra que 100 à la fois. Il y a un correctif qui a réussi les tests unitaires et il peut être trouvé sur le fil ici:

https://github.com/angular-ui/ui-select/pull/716

Fondamentalement, si vous stockez le fichier javascript localement, vous pouvez ajuster la version non minimisée. Tout ce que vous avez à faire est de mettre en œuvre les modifications qu'il a apportées dans la demande de tirage et cela devrait vous aider considérablement. Afin d'appliquer le facteur limitant, jetez un œil à l'exemple modifié ci-dessous:

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

Ce qui précède limitera vos données dans la liste déroulante tout en maintenant le niveau de cohérence nécessaire.

4
BuddhistBeast