【问题标题】:ui-select multiselect is very slow in displaying the choicesui-select multiselect 在显示选项时非常慢
【发布时间】:2015-10-03 00:45:06
【问题描述】:

我遇到了这个问题,不知道怎么解决。我在我的页面中使用了ui-select multiselect。首先,向获取数据的 url 发出 http.get 请求,然后填充 ui-select 选项。数据很大 - 数据长度为 2100。此数据将显示为选项。 (数据在页面加载过程中一开始就获取,并存储在一个数组中)

但问题是每次我单击多选选择一个选项时,填充列表需要 4-5 秒,并且页面变得非常慢。我该怎么做才能减少这个时间?

选择数据存储在数组中,数据类型为字符串数组。

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

在控制器中,

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

$scope.init = function() {

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

$scope.init();

如果不是这种方式,是否有任何其他选项/选项我可以使用且不会延迟显示选择选项?

【问题讨论】:

    标签: angularjs angular-ui ui-select


    【解决方案1】:

    这是 ui-select 中的一个已知问题。我尝试了以下方法,都有效

    1) 有一个解决方法 - 使用

    | limitTo: 100
    

    这将选项显示限制为 100,但可以选择所有选项。查看this thread了解更多详情。

    2) 由于有时需要在选项中显示整个列表,1) 不是一个可行的选项。我使用了不同的库 - selectize.js。这是页面中给出的plunker 演示

    【讨论】:

      【解决方案2】:

      这是装饰 uiSelectChoices 指令的完整解决方案。

      随着用户滚动,项目会逐渐填充。

      还负责卷轴中的搜索。

      也适用于position={auto, up, down}的所有值

      例子

          <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 还有With v0.19.5

      指令

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

      【讨论】:

      • $select.pageOptionsin repeat="person in $select.pageOptions.people 是什么?
      • 感谢您的解释和代码。您的解决方案确实有效。如果你有时间,你应该尝试打开一个 PR 供 ui-select 团队考虑。
      • @envy 尝试plnkr.co/edit/GXkvtT?p=preview 进行分组 - 似乎工作得很好。
      • 我们有一个bug:如果在ui-select渲染后返回att all-choices中的数据(从服务器获取),我们将在pagingOptions.page * pagingOptions.pageSize &lt; allItems.length行收到错误:Cannot read property 'length' of undefined。如果我在这种情况下使用 ng-if,我会工作。你有更好的建议给我吗?
      • 一个 PR 到 ui-select repo 会很棒!
      【解决方案3】:

      如前所述,ui-select 存在很多性能问题,但有解决限制问题的方法。

      如果您遵循 akashrajkn 的方法,那么您会注意到它实际上会删除重要的数据片段,因为它一次只会渲染 100 个。有一个已通过单元测试的修复程序,可以在此处的线程中找到:

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

      基本上,如果您将 javascript 文件存储在本地,那么您可以调整未缩小的版本。您需要做的就是实施他在拉取请求中所做的更改,它应该会有所帮助。为了应用限制因素,请看下面的修改示例:

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

      以上内容将限制下拉列表中的数据,同时保持所需的一致性级别。

      【讨论】:

      • 我试过了,但还是有同样的问题,每次显示选项几乎需要 5 秒,在此期间,页面变得无响应
      • 是的,这是由于 ui-select 的性能问题而发生的。解决这个问题的唯一方法是实际进入文件并删除以 keydown/keyup 事件为中心的大部分无用代码(当然,除非您需要它们,否则无用)。还有其他用于 Angular 的 select2 插件,但似乎它们在 ui-select 中不受支持。当然,您可以随时尝试重写您需要的功能。
      【解决方案4】:

      因为我不能发表评论(没有足够的代表)我写这个作为答案,很抱歉这不是问题的答案。

      @bhantol 我将以下代码行更改为您的解决方案,到目前为止它对我来说非常有效

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

      如果用户开始编写过滤器然后将其删除,这可以防止出现重复的项目。

      另外我刚刚发现如果列表小于 20 项,它将不起作用,所以我更改了:

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

      也许这会对您有所帮助,再次抱歉没有回答问题。

      【讨论】:

      • @bhantol 如果 ngModel 值是从查询字符串传递的,则列表不会预先选择所选项目。或者如果用户点击“重新加载”页面按钮。 person.id as person in $select.pageOptions.people
      猜你喜欢
      • 2016-02-26
      • 1970-01-01
      • 1970-01-01
      • 2013-01-30
      • 1970-01-01
      • 1970-01-01
      • 2012-03-11
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多