【问题标题】:How to put a delay on AngularJS instant search?如何延迟 AngularJS 即时搜索?
【发布时间】:2013-02-24 14:54:34
【问题描述】:

我有一个似乎无法解决的性能问题。我有一个即时搜索,但它有点滞后,因为它开始搜索每个 keyup()

JS:

var App = angular.module('App', []);

App.controller('DisplayController', function($scope, $http) {
$http.get('data.json').then(function(result){
    $scope.entries = result.data;
});
});

HTML:

<input id="searchText" type="search" placeholder="live search..." ng-model="searchText" />
<div class="entry" ng-repeat="entry in entries | filter:searchText">
<span>{{entry.content}}</span>
</div>

JSON 数据甚至没有那么大,只有 300KB,我认为我需要完成的是将搜索延迟约 1 秒以等待用户完成输入,而不是执行操作每次击键。 AngularJS 在内部执行此操作,在阅读了此处的文档和其他主题后,我找不到具体的答案。

如果我能延迟即时搜索,我将不胜感激。

【问题讨论】:

  • 您在 init 应用程序上获得了所有 json...然后您的搜索过滤器在第二次输入时没有获得数据...它正在过滤已经存在的模型。我说的对吗?
  • 下面的答案成功了吗?如果是这样,请接受答案。如果没有,请告诉我,我会进一步澄清。
  • 嗨,杰森,感谢您的回复。我试图玩弄你的代码,但没有运气,搜索对我来说完全停止工作。
  • 没关系,这是我的错,我忽略了一些东西。您的解决方案确实有效。谢谢你:)
  • 在这里查看这个答案,它提供了一个指令,允许您延迟 ng-change:stackoverflow.com/questions/21121460/…

标签: search filter angularjs timeout delay


【解决方案1】:

更新

现在比以往任何时候都更容易(Angular 1.3),只需在模型上添加一个去抖动选项即可。

&lt;input type="text" ng-model="searchStr" ng-model-options="{debounce: 1000}"&gt;

更新插件:
http://plnkr.co/edit/4V13gK

关于 ngModelOptions 的文档:
https://docs.angularjs.org/api/ng/directive/ngModelOptions

旧方法:

这是另一种不依赖于角度本身的方法。

您需要设置一个超时并将当前字符串与过去的版本进行比较,如果两者相同,则执行搜索。

$scope.$watch('searchStr', function (tmpStr)
{
  if (!tmpStr || tmpStr.length == 0)
    return 0;
   $timeout(function() {

    // if searchStr is still the same..
    // go ahead and retrieve the data
    if (tmpStr === $scope.searchStr)
    {
      $http.get('//echo.jsontest.com/res/'+ tmpStr).success(function(data) {
        // update the textarea
        $scope.responseData = data.res; 
      });
    }
  }, 1000);
});

这会进入你的视野:

<input type="text" data-ng-model="searchStr">

<textarea> {{responseData}} </textarea>

强制plunker: http://plnkr.co/dAPmwf

【讨论】:

  • 对我来说,这比接受的答案更容易理解 :) 谢谢!
  • 不存在多个模型更改可能叠加,从而导致重复请求的问题吗?在@JasonAden 的回答中,他通过取消之前排队的事件来解决这个问题。
  • 理论上,如果模型发生变化,但数据保持不变,就会导致多次请求。在实践中,我从未见过它发生。如果您担心,可以添加一个标志来检查该边缘情况。
  • 这是 angular 1.3 的最佳选择
  • 此处警告:如果您有提交或触发的按键事件,它将在没有最新模型值的情况下执行此操作,因为值绑定已消除抖动。例如键入 'foo' 并在立即按键返回时,该值仍将是一个空字符串。
【解决方案2】:

(有关 Angular 1.3 解决方案,请参阅下面的答案。)

这里的问题是每次模型更改时都会执行搜索,这是对输入的每个按键操作。

会有更简洁的方法来做到这一点,但可能最简单的方法是切换绑定,以便您在控制器中定义一个 $scope 属性,您的过滤器在该属性上运行。这样您就可以控制 $scope 变量的更新频率。像这样的:

JS:

var App = angular.module('App', []);

App.controller('DisplayController', function($scope, $http, $timeout) {
    $http.get('data.json').then(function(result){
        $scope.entries = result.data;
    });

    // This is what you will bind the filter to
    $scope.filterText = '';

    // Instantiate these variables outside the watch
    var tempFilterText = '',
        filterTextTimeout;
    $scope.$watch('searchText', function (val) {
        if (filterTextTimeout) $timeout.cancel(filterTextTimeout);

        tempFilterText = val;
        filterTextTimeout = $timeout(function() {
            $scope.filterText = tempFilterText;
        }, 250); // delay 250 ms
    })
});

HTML:

<input id="searchText" type="search" placeholder="live search..." ng-model="searchText" />
<div class="entry" ng-repeat="entry in entries | filter:filterText">
    <span>{{entry.content}}</span>
</div>

【讨论】:

  • 请注意,ng-model 上的 $scope.$watch 不能在 angular-ui bootstrap's modal 内工作
  • 我认为没有 tempFilterText 变量它也可以工作: $scope.$watch('searchText', function (val) { if (filterTextTimeout) $timeout.cancel(filterTextTimeout); filterTextTimeout = $timeout (function() { $scope.filterText = val; }, 250); // 延迟 250 毫秒 })
  • @JosTheeuwen 它只是一个全局变量,被认为是不好的做法,在strict mode 中是不允许的。
【解决方案3】:

在 Angular 1.3 中,我会这样做:

HTML:

<input ng-model="msg" ng-model-options="{debounce: 1000}">

控制器:

$scope.$watch('variableName', function(nVal, oVal) {
    if (nVal !== oVal) {
        myDebouncedFunction();
    }
});

基本上,当msg 范围变量发生变化时,您是在告诉 Angular 运行 myDebouncedFunction()。属性ng-model-options="{debounce: 1000}" 确保msg 每秒只能更新一次。

【讨论】:

    【解决方案4】:
     <input type="text"
        ng-model ="criteria.searchtext""  
        ng-model-options="{debounce: {'default': 1000, 'blur': 0}}"
        class="form-control" 
        placeholder="Search" >
    

    现在我们可以设置 ng-model-options 随时间去抖动,当模糊时,需要立即更改模型,否则在保存时如果延迟未完成,它将具有较旧的值。

    【讨论】:

      【解决方案5】:

      对于那些在 HTML 标记中使用 keyup/keydown 的人。 这不使用手表。

      JS

      app.controller('SearchCtrl', function ($scope, $http, $timeout) {
        var promise = '';
        $scope.search = function() {
          if(promise){
            $timeout.cancel(promise);
          }
          promise = $timeout(function() {
          //ajax call goes here..
          },2000);
        };
      });
      

      HTML

      <input type="search" autocomplete="off" ng-model="keywords" ng-keyup="search()" placeholder="Search...">
      

      【讨论】:

        【解决方案6】:

        Angularjs 的去抖动/节流模型更新:http://jsfiddle.net/lgersman/vPsGb/3/

        在你的情况下,没有什么比在 jsfiddle 代码中使用指令这样的:

        <input 
            id="searchText" 
            type="search" 
            placeholder="live search..." 
            ng-model="searchText" 
            ng-ampere-debounce
        />
        

        它基本上是一小段代码,由一个名为“ng-ampere-debounce”的单个角度指令组成,该指令利用http://benalman.com/projects/jquery-throttle-debounce-plugin/,可以附加到任何dom元素。该指令重新排序附加的事件处理程序,以便它可以控制何时限制事件。

        您可以将其用于节流/去抖动 * 模型角度更新 * 角度事件处理程序 ng-[事件] * jquery 事件处理程序

        看看:http://jsfiddle.net/lgersman/vPsGb/3/

        该指令将成为 Orangevolt Ampere 框架 (https://github.com/lgersman/jquery.orangevolt-ampere) 的一部分。

        【讨论】:

          【解决方案7】:

          仅适用于此处重定向的用户:

          正如Angular 1.3 中介绍的,您可以使用ng-model-options 属性:

          <input 
                 id="searchText" 
                 type="search" 
                 placeholder="live search..." 
                 ng-model="searchText"
                 ng-model-options="{ debounce: 250 }"
          />
          

          【讨论】:

            【解决方案8】:

            我相信解决这个问题的最好方法是使用 Ben Alman 的插件jQuery throttle / debounce。在我看来,没有必要延迟表单中每个字段的事件。

            只需将您的 $scope.$watch 处理函数包装在 $.debounce 中,如下所示:

            $scope.$watch("searchText", $.debounce(1000, function() {
                console.log($scope.searchText);
            }), true);
            

            【讨论】:

            • 您需要将其包装在 $scope.$apply
            【解决方案9】:

            另一个解决方案是为模型更新添加延迟功能。简单的指令似乎起到了作用:

            app.directive('delayedModel', function() {
                return {
                    scope: {
                        model: '=delayedModel'
                    },
                    link: function(scope, element, attrs) {
            
                        element.val(scope.model);
            
                        scope.$watch('model', function(newVal, oldVal) {
                            if (newVal !== oldVal) {
                                element.val(scope.model);        
                            }
                        });
            
                        var timeout;
                        element.on('keyup paste search', function() {
                            clearTimeout(timeout);
                            timeout = setTimeout(function() {
                                scope.model = element[0].value;
                                element.val(scope.model);
                                scope.$apply();
                            }, attrs.delay || 500);
                        });
                    }
                };
            });
            

            用法:

            <input delayed-model="searchText" data-delay="500" id="searchText" type="search" placeholder="live search..." />
            

            所以您只需使用delayed-model 代替ng-model 并定义所需的data-delay

            演示:http://plnkr.co/edit/OmB4C3jtUD2Wjq5kzTSU?p=preview

            【讨论】:

            • 嘿!你能解释一下model: '=delayedModel' 是如何工作的吗?或者你能指出我可以找到它的链接吗?
            • @AkashAgrawal 这是一种双向数据绑定。阅读这里docs.angularjs.org/api/ng.$compile
            • @dfsq 我正在使用 ng-change,它曾经在文本发生更改时触发。但是在定义指令时我不能使用它。 element.on('change') 仅在模糊时触发。 (1) 有解决办法吗? (2) 如何在文本变化时调用控制器的函数?
            【解决方案10】:

            我用一个指令解决了这个问题,基本上它的作用是将真正的 ng-model 绑定到我在指令中观察的特殊属性上,然后使用去抖动服务更新我的指令属性,所以用户观看他绑定到 debounce-model 而不是 ng-model 的变量。

            .directive('debounceDelay', function ($compile, $debounce) {
            return {
              replace: false,
              scope: {
                debounceModel: '='
              },
              link: function (scope, element, attr) {
                var delay= attr.debounceDelay;
                var applyFunc = function () {
                  scope.debounceModel = scope.model;
                }
                scope.model = scope.debounceModel;
                scope.$watch('model', function(){
                  $debounce(applyFunc, delay);
                });
                attr.$set('ngModel', 'model');
                element.removeAttr('debounce-delay'); // so the next $compile won't run it again!
            
               $compile(element)(scope);
              }
            };
            });
            

            用法:

            <input type="text" debounce-delay="1000" debounce-model="search"></input>
            

            在控制器中:

                $scope.search = "";
                $scope.$watch('search', function (newVal, oldVal) {
                  if(newVal === oldVal){
                    return;
                  }else{ //do something meaningful }
            

            jsfiddle 中的演示:http://jsfiddle.net/6K7Kd/37/

            $debounce 服务可以在这里找到:http://jsfiddle.net/Warspawn/6K7Kd/

            灵感来自 finallyBind 指令 http://jsfiddle.net/fctZH/12/

            【讨论】:

              【解决方案11】:

              Angular 1.3 将有 ng-model-options debounce,但在那之前,你必须使用 Josue Ibarra 所说的计时器。然而,在他的代码中,他在每次按键时都会启动一个计时器。此外,他正在使用 setTimeout,而在 Angular 中,必须使用 $timeout 或在 setTimeout 结束时使用 $apply。

              【讨论】:

                【解决方案12】:

                为什么大家都想用手表?你也可以使用一个函数:

                var tempArticleSearchTerm;
                $scope.lookupArticle = function (val) {
                    tempArticleSearchTerm = val;
                
                    $timeout(function () {
                        if (val == tempArticleSearchTerm) {
                            //function you want to execute after 250ms, if the value as changed
                
                        }
                    }, 250);
                }; 
                

                【讨论】:

                  【解决方案13】:

                  我认为这里最简单的方法是预加载 json 或在$dirty 上加载一次,然后过滤器搜索将处理其余部分。这将为您节省额外的 http 调用,并且使用预加载的数据更快。记忆会受到伤害,但这是值得的。

                  【讨论】:

                    猜你喜欢
                    • 2015-10-13
                    • 1970-01-01
                    • 1970-01-01
                    • 2018-08-02
                    • 1970-01-01
                    • 1970-01-01
                    • 2015-10-10
                    • 2021-05-20
                    • 1970-01-01
                    相关资源
                    最近更新 更多