Як поставити затримку на миттєвий пошук 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 ... а потім ваш фільтр пошуку не отримує дані вдруге під час введення тексту ... це фільтрує вже існуючу модель. Я правильно?
Максим

Чи виправдана відповідь нижче? Якщо так, прийміть відповідь. Якщо ні, то дайте мені знати, і я ще більше уточню.
Джейсон Аден

Ей, Джейсон, дякую за відповідь. Я намагався пограти з вашим кодом, але не пощастило, пошук перестає працювати для мене.
мозкова гребінь

Не маю на увазі, мені було погано, що я щось не помітив. Ваше рішення справді працює. Дякую :)
braincomb

Подивіться на цю відповідь тут, яка містить директиву, яка дозволяє затримати ng-change: stackoverflow.com/questions/21121460/…
Doug

Відповіді:


121

(Дивіться відповідь нижче для рішення Angular 1.3.)

Проблема тут полягає в тому, що пошук буде виконуватися щоразу, коли модель змінюється, а це кожна дія клавіатури на вході.

Існували б більш чіткі способи зробити це, але, мабуть, найпростішим способом було б переключити прив'язку так, щоб у вашому контролері, на якому працює ваш фільтр, було визначено властивість $ range. Таким чином ви можете контролювати, як часто оновлюється змінна $ range. Щось на зразок цього:

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>

Зауважте, що $ range. $ Watch не ng-modelбуде працювати всередині модального кутового завантаження
Hendy Irawan

1
Я думаю, що він також буде працювати без змінної tempFilterText: $ obseg. $ Watch ('searchText', функція (val) {if (filterTextTimeout) $ timeout.cancel (filterTextTimeout); filterTextTimeout = $ timeout (function () {$ obseg. filterText = val;}, 250); // затримка 250 мс})
Jos Theeuwen

@JosTheeuwen - це просто глобальна змінна, яка вважається поганою практикою і заборонена в суворому режимі .
mb21

301

ОНОВЛЕННЯ

Тепер це простіше, ніж будь-коли (кутовий 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 він піклується про це, скасовуючи раніше події в черзі.
Бласковіч

Теоретично, якщо модель зазнає змін, але дані залишаються тими ж, це призведе до декількох запитів. На практиці я ніколи не бачив, щоб це сталося. Ви можете додати прапор, щоб перевірити цей край, якщо ви переживаєте.
Джозеу Олександр Ібарра

Це набагато найкращий вибір для кутового 1.3
Marcus W

Попередження тут: Якщо у вас є подія натискання клавіші, яка подає або спрацьовує, вона буде робити це без останнього значення моделі, оскільки прив'язка значення знімається. наприклад, введіть 'foo', і при негайному поверненні клавіші значення все ще буде порожнім рядком.
весело

34

У куті 1.3 я би зробив це:

HTML:

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

Контролер:

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

В основному ви говорите кутовий запуск 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 debounce з часом, і коли розмиття, модель потрібно негайно змінити, інакше при збереженні вона матиме старіше значення, якщо затримка не буде завершена.


9

Для тих, хто використовує клавіатуру / клавішу в розмітці 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- [подія] * обробників подій 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 за допомогою плагіна Бена Альмана . На мою думку, не потрібно затягувати події кожного окремого поля у вашій формі.

Просто оберніть свою функцію $ range. $ Обробка годинника в $ .debounce так:

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

Вам потрібно буде
зафіксувати

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-модель до спеціального атрибута, який я спостерігаю в директиві, а потім за допомогою служби дебютування оновлюю свій атрибут директиви, тож користувач спостерігає за змінною, що він прив’язується до дебютування-моделі замість 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/

Натхненний директивою в кінцевому підсумку http://jsfiddle.net/fctZH/12/


0

У кутовій версії 1.3 буде розміщення ng-model-options, але до цього часу ви повинні використовувати таймер, як сказав Джозу Ібарра. Однак у своєму коді він запускає таймер при кожному натисканні клавіш. Крім того, він використовує setTimeout, коли в Angular потрібно використовувати $ timeout або $ застосовувати в кінці 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.