Come ritardare la ricerca istantanea di AngularJS?


147

Ho un problema di prestazioni che non riesco a risolvere. Ho una ricerca istantanea ma è un po 'ritardata, poiché inizia a cercare su ciascuna 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>

I dati JSON non sono nemmeno così grandi, solo 300 KB, penso che ciò di cui ho bisogno è di mettere un ritardo di ~ 1 secondo nella ricerca per attendere che l'utente finisca di digitare, invece di eseguire l'azione su ogni sequenza di tasti. AngularJS lo fa internamente, e dopo aver letto documenti e altri argomenti qui non sono riuscito a trovare una risposta specifica.

Gradirei qualsiasi suggerimento su come posso ritardare la ricerca istantanea.


1
Stai ricevendo tutto json sull'app init ... e quindi il tuo filtro di ricerca non ottiene i dati una seconda volta durante la digitazione ... sta filtrando il modello già esistente. Ho ragione?
Maksym,

La risposta qui sotto ha funzionato? In tal caso, si prega di accettare la risposta. In caso contrario, fammi sapere e chiarirò ulteriormente.
Jason Aden,

Ehi Jason, grazie per la risposta. Stavo cercando di giocare con il tuo codice ma senza fortuna, la ricerca smette di funzionare del tutto per me.
Braincomb

Non importa, è stato un peccato che abbia trascurato qualcosa. La tua soluzione funziona davvero. Grazie :)
Braincomb

Dai un'occhiata a questa risposta qui, che fornisce una direttiva che ti consente di ritardare la modifica: stackoverflow.com/questions/21121460/…
Doug

Risposte:


121

(Vedi la risposta sotto per una soluzione Angular 1.3.)

Il problema qui è che la ricerca verrà eseguita ogni volta che cambia il modello, ovvero ogni azione di keyup su un input.

Ci sarebbero modi più puliti per farlo, ma probabilmente il modo più semplice sarebbe cambiare l'associazione in modo che tu abbia una proprietà $ scope definita all'interno del tuo Controller su cui opera il tuo filtro. In questo modo puoi controllare con che frequenza viene aggiornata quella variabile $ scope. Qualcosa come questo:

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>

Nota che $ scope. $ Watch su an ng-modelnon funzionerà all'interno del modale del bootstrap angular-ui
Hendy Irawan,

1
Penso che funzionerà anche senza la variabile tempFilterText: $ scope. $ Watch ('searchText', function (val) {if (filterTextTimeout) $ timeout.cancel (filterTextTimeout); filterTextTimeout = $ timeout (function () {$ scope. filterText = val;}, 250); // delay 250 ms})
Jos Theeuwen

@JosTheeuwen è quindi semplicemente una variabile globale che è considerata una cattiva pratica e non consentita in modalità rigorosa .
mb21

301

AGGIORNARE

Ora è più facile che mai (Angular 1.3), basta aggiungere un'opzione di rimbalzo sul modello.

<input type="text" ng-model="searchStr" ng-model-options="{debounce: 1000}">

Plunker aggiornato:
http://plnkr.co/edit/4V13gK

Documentazione su ngModelOptions:
https://docs.angularjs.org/api/ng/directive/ngModelOptions

Vecchio metodo:

Ecco un altro metodo senza dipendenze oltre la stessa angolare.

È necessario impostare un timeout e confrontare la stringa corrente con la versione precedente, se entrambi sono uguali, esegue la ricerca.

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

e questo va a tuo avviso:

<input type="text" data-ng-model="searchStr">

<textarea> {{responseData}} </textarea>

Il plunker obbligatorio: http://plnkr.co/dAPmwf


2
Per me è una risposta molto più comprensibile di quella accettata :) Grazie!
OZ_

3
Non esiste un problema a causa del quale si possono accumulare più modifiche al modello, causando così richieste duplicate? Nella risposta di @ JasonAden, si occupa di questo annullando gli eventi precedentemente accodati.
Blaskovicz,

In teoria, se il modello subisce una modifica, ma i dati rimangono gli stessi, causerebbero più richieste. In pratica non l'ho mai visto accadere. Puoi aggiungere una bandiera per verificare la presenza di quella custodia perimetrale se sei preoccupato.
Josue Alexander Ibarra,

Questa è di gran lunga la scelta migliore per l'angolare 1.3
Marcus W

Avviso qui: se si dispone di un evento keypress che invia o innesca, lo farà senza l'ultimo valore del modello poiché l'associazione del valore è stata annullata. ad esempio, digita "pippo" e quando premi immediatamente un tasto, il valore sarà comunque una stringa vuota.
jbodily

34

In Angular 1.3 farei questo:

HTML:

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

controller:

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

Fondamentalmente stai dicendo angolare da eseguire myDebouncedFunction(), quando la msgvariabile dell'ambito cambia. L'attributo ng-model-options="{debounce: 1000}"assicura che msgpossa essere aggiornato solo una volta al secondo.


10
 <input type="text"
    ng-model ="criteria.searchtext""  
    ng-model-options="{debounce: {'default': 1000, 'blur': 0}}"
    class="form-control" 
    placeholder="Search" >

Ora possiamo impostare il ng-model-options debounce con il tempo e quando la sfocatura deve essere cambiata immediatamente, altrimenti al salvataggio avrà un valore più vecchio se il ritardo non è completato.


9

Per coloro che usano il keyup / keydown nel markup HTML. Questo non utilizza 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...">

6

Aggiornamenti del modello annullati / limitati per angularjs: http://jsfiddle.net/lgersman/vPsGb/3/

Nel tuo caso non c'è altro da fare che usare la direttiva nel codice jsfiddle in questo modo:

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

Fondamentalmente è un piccolo codice composto da un'unica direttiva angolare denominata "ng-ampere-debounce" che utilizza http://benalman.com/projects/jquery-throttle-debounce-plugin/ che può essere collegato a qualsiasi elemento dom. La direttiva riordina i gestori di eventi collegati in modo da poter controllare quando limitare gli eventi.

Puoi usarlo per limitare / rimbalzare * aggiornamenti angolari del modello * gestore di eventi angolare ng- [evento] * gestori di eventi jquery

Dai un'occhiata: http://jsfiddle.net/lgersman/vPsGb/3/

La direttiva farà parte del framework Orangevolt Ampere ( https://github.com/lgersman/jquery.orangevolt-ampere ).


6

Solo per gli utenti reindirizzati qui:

Come introdotto in Angular 1.3puoi usare l' attributo ng-model-options :

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

5

Credo che il modo migliore per risolvere questo problema sia usare il plugin jQuery throttle / debounce di Ben Alman . Secondo me non è necessario ritardare gli eventi di ogni singolo campo nel tuo modulo.

Basta avvolgere il $ scope. $ Guarda la funzione di gestione in $ .debounce in questo modo:

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

Dovrai racchiuderlo in $ scope. $
Apply

3

Un'altra soluzione è aggiungere una funzionalità di ritardo all'aggiornamento del modello. La semplice direttiva sembra fare un trucco:

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

Uso:

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

Quindi basta usare delayed-modelal posto di ng-modele definire desiderato data-delay.

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


Hey! puoi spiegare come model: '=delayedModel'funziona? O puoi indicarmi un link dove posso trovarlo?
Akash Agrawal,

@AkashAgrawal È un'associazione di dati bidirezionale. Leggi qui docs.angularjs.org/api/ng.$compile
dfsq

1
@dfsq Stavo usando ng-change e si attivava ogni volta che c'era una modifica nel testo. Ma non posso usarlo quando viene definita una direttiva. element.on('change')si innesca solo su sfocatura. (1) C'è un lavoro in giro? (2) come chiamare una funzione del controller sulla modifica del testo?
Vyas Rao,

0

Ho risolto questo problema con una direttiva che fondamentalmente ciò che fa è legare il vero modello ng su un attributo speciale che guardo nella direttiva, quindi usando un servizio di debounce aggiorno il mio attributo direttiva, quindi l'utente guarda sulla variabile che si lega a debounce-model anziché a 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);
  }
};
});

Uso:

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

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

il servizio $ debounce può essere trovato qui: http://jsfiddle.net/Warspawn/6K7Kd/

Ispirato dalla direttiva eventualmenteBind http://jsfiddle.net/fctZH/12/


0

Angular 1.3 avrà il debounce di ng-model-options, ma fino ad allora, devi usare un timer come ha detto Josue Ibarra. Tuttavia, nel suo codice lancia un timer ad ogni pressione del tasto. Inoltre, sta usando setTimeout, quando in Angular bisogna usare $ timeout o usare $ apply alla fine di setTimeout.


0

Perché tutti vogliono usare l'orologio? Puoi anche usare una funzione:

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

Penso che il modo più semplice qui sia di precaricare il json o caricarlo una volta, $dirtyquindi la ricerca del filtro si occuperà del resto. Questo ti farà risparmiare le chiamate http extra ed è molto più veloce con i dati precaricati. La memoria farà male, ma ne vale la pena.

Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.