Как поставить задержку на мгновенный поиск AngularJS?

У меня проблема с производительностью, которую я не могу решить. У меня есть мгновенный поиск, но он несколько запаздывает, поскольку он начинает поиск по каждому 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 даже не такие большие, всего 300 КБ. Думаю, мне нужно сделать так, чтобы при поиске была задержана ~ 1 секунда, чтобы пользователь дождался завершения ввода вместо того, чтобы выполнять действие над каждое нажатие клавиши. AngularJS делает это внутренне, и после прочтения документации и других тем здесь я не смог найти конкретный ответ.

Буду признателен за любые указания о том, как я могу отложить мгновенный поиск.

Ответ 1

(см. ответ ниже для решения 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>

Ответ 2

UPDATE

Теперь это проще, чем когда-либо (Angular 1.3), просто добавьте опцию debounce на модель.

<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

Старый метод:

Здесь другой метод без зависимостей вне angular.

Вам нужно установить тайм-аут и сравнить текущую строку с прошлой версией, если оба они одинаковы, тогда он выполняет поиск.

$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

Ответ 3

В Angular 1.3 я бы сделал это:

HTML:

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

Контроллер:

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

По сути, вы говорите angular запускать myDebouncedFunction(), когда изменяется переменная области видимости msg. Атрибут 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" >

Теперь мы можем установить debg ng-model-options со временем, а при размытии модель должна быть немедленно изменена, иначе при сохранении она будет иметь более старое значение, если задержка не завершена.

Ответ 5

Для тех, кто использует keyup/keydown в разметке HTML. Это не использует часы.

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

В основном это небольшая часть кода, состоящая из одной директивы angular с именем ng-ampere-debounce, использующей http://benalman.com/projects/jquery-throttle-debounce-plugin/, которая могут быть прикреплены к любому элементу dom. Директива переупорядочивает подключенные обработчики событий, чтобы она могла контролировать, когда дросселировать события.

Вы можете использовать его для дросселирования /debouncing * модель angular обновления * angular обработчик событий ng- [event] * обработчики событий 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);

Ответ 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

Ответ 10

Я решил эту проблему с директивой о том, что в основном это то, что она связывает реальную ng-модель с специальным атрибутом, который я смотрю в директиве, а затем с помощью службы debounce я обновляю свой атрибут директивы, поэтому пользователь наблюдает за переменную, которую он привязывает к модели debounce вместо 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/

Вдохновленный директивой finallyBind http://jsfiddle.net/fctZH/12/

Ответ 11

Angular 1.3 будет отображаться debg ng-model-options, но до этого вам нужно использовать таймер, например, Josue Ibarra. Однако в своем коде он запускает таймер при каждом нажатии клавиши. Кроме того, он использует setTimeout, когда в Angular нужно использовать $timeout или использовать $apply в конце setTimeout.

Ответ 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-звонков и намного быстрее с предустановленными данными. Память будет больно, но ее стоит.