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