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

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


1
Вы получаете все json в приложении init ... и тогда ваш поисковый фильтр не получает данные второй раз при наборе ... он фильтрует уже существующую модель. Я прав?
— Максим

Сработал ответ ниже? Если это так, пожалуйста, примите ответ. Если нет, дайте мне знать, и я уточню.
— Джейсон Аден

Привет, Джейсон, спасибо за ответ. Я пытался поиграться с вашим кодом, но не повезло, поиск перестает работать на меня.
— мозговой ком

Неважно, это было плохо, я что-то упустил. Ваше решение действительно работает. Спасибо :)
— мозговой ком

Посмотрите на этот ответ здесь, который предоставляет директиву, которая позволяет вам задержать изменение ng: stackoverflow.com/questions/21121460/…
— Дуг

Ответы:


121

(См. Ответ ниже для углового решения 1.3.)

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

Были бы более чистые способы сделать это, но, вероятно, самый простой способ - это переключить привязку, чтобы у вас было свойство $ 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

1
Я думаю, что это также будет работать без переменной tempFilterText: $ scope. $ Watch ('searchText', function (val) {if (filterTextTimeout) $ timeout.cancel (filterTextTimeout); filterTextTimeout = $ timeout (function () {$ scope). filterText = val;}, 250); // задержка 250 мс})
— Jos Theeuwen

@JosTheeuwen - это просто глобальная переменная, которая считается плохой практикой и не допускается в строгом режиме .
— mb21

301

ОБНОВИТЬ

Теперь это проще, чем когда-либо (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

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

Вот еще один метод без зависимостей, кроме самого углового.

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

$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 он заботится об этом, отменяя ранее поставленные в очередь события.
— Бласкович

Теоретически, если модель претерпевает изменения, но данные остаются прежними, это вызовет несколько запросов. На практике я никогда не видел, чтобы это произошло. Вы можете добавить флаг, чтобы проверить этот крайний случай, если вы беспокоитесь.
— Хосе Александр Ибарра

На данный момент это лучший выбор для угловых 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();
    }
});

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

Для тех, кто использует 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
/>

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

Вы можете использовать его для регулирования / отклонения * угловых обновлений модели * обработчика угловых событий ng- [event] * обработчиков событий jquery

Посмотрите: http://jsfiddle.net/lgersman/vPsGb/3/

Директива будет частью структуры Orangevolt Ampere ( https://github.com/lgersman/jquery.orangevolt-ampere ).


6

Просто для пользователей, перенаправленных сюда:

Как было показано, Angular 1.3вы можете использовать атрибут ng-model-options :

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

5

Я считаю, что лучший способ решить эту проблему - использовать плагин Бен Алмана jQuery throttle / debounce . На мой взгляд, нет необходимости откладывать события каждого поля в вашей форме.

Просто оберните вашу функцию обработки $ scope. $ Watch в $ .debounce следующим образом:

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

Вам нужно будет обернуть это в $ scope. $
— Apply

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'работает? Или вы можете указать мне ссылку, где я могу найти его?
— Акаш Агравал

@AkashAgrawal Это двусторонняя привязка данных. Прочитайте об этом здесь docs.angularjs.org/api/ng.$compile
— dfsq

1
@dfsq Я использовал ng-change, и он использовался для запуска при изменении текста. Но я не могу использовать его, когда директива определена. element.on('change')срабатывает только на размытие. (1) Есть ли обход? (2) как вызвать функцию контроллера при изменении текста?
— Вьяс Рао

0

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

Вдохновлен в конечном итоге директивой Bind http://jsfiddle.net/fctZH/12/


0

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


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

Используя наш сайт, вы подтверждаете, что прочитали и поняли нашу Политику в отношении файлов cookie и Политику конфиденциальности.
Licensed under cc by-sa 3.0 with attribution required.