如何延迟AngularJS即时搜索?


147

我有一个似乎无法解决的性能问题。我有一个即时搜索功能,但是有点麻烦,因为它开始在每个位置上搜索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在内部执行此操作,在阅读了文档和此处的其他主题之后,我找不到特定的答案。

我将不胜感激如何延迟即时搜索的任何指示。


1
您正在初始化应用程序上获取所有json ...然后您的搜索过滤器第二次没有得到数据...正在过滤现有模型。我对么?
Maksym

下面的答案解决了吗?如果是这样,请接受答案。如果没有,请告诉我,我会进一步澄清。
杰森·亚丁

嘿杰森,感谢您的回复。我正在尝试使用您的代码,但是没有运气,搜索对我来说完全停止了工作。
braincomb 2013年

没关系,这是我的遗憾,我忽略了一些东西。您的解决方案确实有效。谢谢:)
braincomb 2013年

在这里看看这个答案,它提供了一条指令,允许您延迟ng-change:stackoverflow.com/questions/21121460/…–
Doug

Answers:


121

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

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

会有更清洁的方法来执行此操作,但是最简单的方法可能是切换绑定,以便在您的Controller上定义了一个$ 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>

请注意,在$ scope。$ watch ng-model上将无法在angular-ui引导程序的模式内使用
Hendy Irawan 2014年

1
我认为它也可以在没有tempFilterText变量的情况下工作:$ scope。$ watch('searchText',function(val){if(filterTextTimeout)$ timeout.cancel(filterTextTimeout); filterTextTimeout = $ timeout(function(){$ scope。 filterText = val;},250); //延迟250 ms})
Jos Theeuwen 2014年

@JosTheeuwen它只是一个全局变量,被认为是不好的实践,在严格模式下是不允许的。
mb21 2015年

301

更新

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

<input type="text" ng-model="searchStr" ng-model-options="{debounce: 1000}">

更新的插件: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>

强制性放声工具:http://plnkr.co/dAPmwf


2
对我来说,答案比接受的要理解得多:)谢谢!
OZ_

3
是否存在多个模型更改可以堆叠从而导致重复请求的问题?在@JasonAden的回答中,他通过取消先前排队的事件来解决此问题。
Blaskovicz 2014年

从理论上讲,如果模型发生更改,但是数据保持不变,则将导致多个请求。在实践中,我从未见过这种情况。如果您担心,可以添加一个标志来检查该边缘情况。
Josue Alexander Ibarra 2014年

这是通过对角1.3远远优越的选择
马库斯W¯¯

此处警告:如果您有一个提交或触发的按键事件,那么它将取消操作而没有最新的模型值,因为值绑定被去抖动了。例如,键入“ foo”,并在立即按键返回时,该值仍为空字符串。
jbodily

34

在Angular 1.3中,我可以这样做:

HTML:

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

控制器:

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

基本上,您是myDebouncedFunction()在使msgscope变量更改时告诉angular运行。该属性ng-model-options="{debounce: 1000}"确保msg每秒只能更新一次。


10
 <input type="text"
    ng-model ="criteria.searchtext""  
    ng-model-options="{debounce: {'default': 1000, 'blur': 0}}"
    class="form-control" 
    placeholder="Search" >

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


9

对于在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
/>

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

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

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

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



5

我相信解决此问题的最佳方法是使用Ben Alman的插件jQuery油门/反跳。我认为,无需延迟表单中每个字段的事件。

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

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

您需要将其包装在$ scope。$ apply中
Aakil Fernandes

3

另一个解决方案是为模型更新添加延迟功能。简单的指令似乎可以解决问题:

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'工作吗?还是可以将我指向可以找到它的链接?
Akash Agrawal 2014年

@AkashAgrawal这是两种方式的数据绑定。在此处阅读有关文档docs.angularjs.org/api/ng.$compile
dfsq 2014年

1
@dfsq我正在使用ng-change,它用于在文本发生更改时触发。但是定义指令时我不能使用它。element.on('change')仅在模糊时触发。(1)周围有工作吗?(2)如何在文本更改时调用控制器的功能?
Vyas Rao 2014年

0

我用指令解决了这个问题,该指令的基本作用是将实际ng-model绑定到我在指令中观察到的特殊属性上,然后使用去抖动服务更新我的指令属性,因此用户可以观察到他绑定到反跳模型而不是ng模型。

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

受finalBind指令 http://jsfiddle.net/fctZH/12/的启发


0

Angular 1.3将对ng-model-options进行反跳,但是在那之前,您必须使用Josue Ibarra所说的计时器。但是,在他的代码中,每次按键时都会启动一个计时器。另外,他正在使用setTimeout,而在Angular中,则必须使用$ timeout或在setTimeout的末尾使用$ apply。


0

为什么每个人都想使用手表?您还可以使用一个函数:

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

0

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

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.