How to put a delay on AngularJS instant search?

2020-01-26 13:03发布

I have a performance issue that I can't seem to address. I have an instant search but it's somewhat laggy, since it starts searching on each 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>

The JSON data isn't even that large, 300KB only, I think what I need to accomplish is to put a delay of ~1 sec on the search to wait for the user to finish typing, instead of performing the action on each keystroke. AngularJS does this internally, and after reading docs and other topics on here I couldn't find a specific answer.

I would appreciate any pointers on how I can delay the instant search.

13条回答
Bombasti
2楼-- · 2020-01-26 13:07

For those who uses keyup/keydown in the HTML markup. This doesn't uses watch.

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...">
查看更多
冷血范
3楼-- · 2020-01-26 13:09

Debounced / throttled model updates for angularjs : http://jsfiddle.net/lgersman/vPsGb/3/

In your case there is nothing more to do than using the directive in the jsfiddle code like this:

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

Its basically a small piece of code consisting of a single angular directive named "ng-ampere-debounce" utilizing http://benalman.com/projects/jquery-throttle-debounce-plugin/ which can be attached to any dom element. The directive reorders the attached event handlers so that it can control when to throttle events.

You can use it for throttling/debouncing * model angular updates * angular event handler ng-[event] * jquery event handlers

Have a look : http://jsfiddle.net/lgersman/vPsGb/3/

The directive will be part of the Orangevolt Ampere framework (https://github.com/lgersman/jquery.orangevolt-ampere).

查看更多
爷的心禁止访问
4楼-- · 2020-01-26 13:09

I think the easiest way here is to preload the json or load it once on$dirty and then the filter search will take care of the rest. This'll save you the extra http calls and its much faster with preloaded data. Memory will hurt, but its worth it.

查看更多
虎瘦雄心在
5楼-- · 2020-01-26 13:11

I solved this problem with a directive that basicly what it does is to bind the real ng-model on a special attribute which I watch in the directive, then using a debounce service I update my directive attribute, so the user watch on the variable that he bind to debounce-model instead of 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);
  }
};
});

Usage:

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

And in the controller :

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

Demo in jsfiddle: http://jsfiddle.net/6K7Kd/37/

the $debounce service can be found here: http://jsfiddle.net/Warspawn/6K7Kd/

Inspired by eventuallyBind directive http://jsfiddle.net/fctZH/12/

查看更多
仙女界的扛把子
6楼-- · 2020-01-26 13:12

Another solution is to add a delay functionality to model update. The simple directive seems to do a trick:

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

Usage:

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

So you just use delayed-model in place of ng-model and define desired data-delay.

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

查看更多
成全新的幸福
7楼-- · 2020-01-26 13:17

In Angular 1.3 I would do this:

HTML:

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

Controller:

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

Basically you're telling angular to run myDebouncedFunction(), when the the msg scope variable changes. The attribute ng-model-options="{debounce: 1000}" makes sure that msg can only update once a second.

查看更多
登录 后发表回答