web-dev-qa-db-fra.com

Comment mettre un délai sur la recherche instantanée AngularJS?

Je suis nouveau chez AngularJS et j'ai un problème de performances que je n'arrive pas à résoudre. J'ai une recherche instantanée, mais elle est un peu lente, car elle commence à chercher sur chaque 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>

Les données JSON n’étant même pas aussi volumineuses, 300 Ko seulement, je pense que ce que je dois accomplir est de mettre un délai d’environ 1 seconde sur la recherche afin d’attendre que l’utilisateur ait fini de taper au lieu d’effectuer l’action sur chaque frappe. AngularJS le fait en interne, et après avoir lu la documentation et d'autres sujets ici, je n'ai pas pu trouver de réponse spécifique.

J'apprécierais tous les conseils sur la façon dont je peux retarder la recherche instantanée .. Merci.

146
braincomb

(Voir la réponse ci-dessous pour une solution angulaire 1.3.)

Le problème ici est que la recherche sera exécutée chaque fois que le modèle change, ce qui correspond à chaque action de frappe sur une entrée.

Il y aurait des moyens plus propres de le faire, mais le moyen le plus simple serait probablement de changer la liaison de sorte que vous ayez une propriété $ scope définie dans votre contrôleur sur lequel votre filtre fonctionne. De cette façon, vous pouvez contrôler la fréquence de mise à jour de cette variable $ scope. Quelque chose comme ça:

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>
120
Jason Aden

METTRE &AGRAVE; JOUR

Maintenant, c'est plus facile que jamais (Angular 1.3), ajoutez simplement une option anti-rebond sur le modèle.

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

Plunker mis à jour:
http://plnkr.co/edit/4V13gK

Documentation sur ngModelOptions:
https://docs.angularjs.org/api/ng/directive/ngModelOptions

Ancienne méthode:

Voici une autre méthode sans dépendances au-delà de angular elle-même.

Vous devez définir un délai d'attente et comparer votre chaîne actuelle à la version précédente. Si les deux sont identiques, la recherche est effectuée.

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

et cela va dans votre vue:

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

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

Le plongeur obligatoire: http://plnkr.co/dAPmwf

295

Dans Angular 1.3, je ferais ceci:

HTML:

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

Manette:

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

Fondamentalement, vous dites à Angular d’exécuter myDebounceFunction () lorsque la variable de champ msg change. L'attribut ng-model-options = "{debounce: 1000}" garantit que msg ne peut être mis à jour qu'une fois par seconde.

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

Nous pouvons maintenant définir les options de modèle ng anti-rebond avec le temps et lorsque le flou est nécessaire, le modèle doit être changé immédiatement, sinon, la valeur enregistrée sera plus ancienne si le retard n’est pas terminé. 

10
Ali Adravi

Mises à jour modélisées avec Debounce/throttled pour angularjs: http://jsfiddle.net/lgersman/vPsGb/3/

Dans votre cas, il n’ya rien de plus à faire que d’utiliser la directive dans le code jsfiddle comme ceci:

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

C'est en gros un petit morceau de code consistant en une directive angulaire unique appelée "ng-ampere-debounce" utilisant http://benalman.com/projects/jquery-throttle-debounce-plugin/ qui peut être attaché à n'importe quel élément dom. La directive réordonne les gestionnaires d'événements attachés afin qu'elle puisse contrôler quand limiter les événements.

Vous pouvez l'utiliser pour limiter/annuler les changements * Modèles mises à jour angulaires * Gestionnaire d'événements angulaires ng- [événement] * Jquery gestionnaires d'événements

Regardez: http://jsfiddle.net/lgersman/vPsGb/3/

La directive fera partie du cadre Orangevolt Ampere ( https://github.com/lgersman/jquery.orangevolt-ampere ). 

6
lgersman

Juste pour les utilisateurs redirigés ici:

Comme introduit dans Angular 1.3, vous pouvez utiliser ng-model-options attribut:

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

Pour ceux qui utilisent keyup/keydown dans le balisage HTML .Ne utilise pas 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
Vinoth

Je crois que le meilleur moyen de résoudre ce problème consiste à utiliser le plugin de Ben Alman jQuery throttle/debounce . À mon avis, il n'est pas nécessaire de retarder les événements de chaque champ de votre formulaire.

Enveloppez simplement votre fonction de surveillance de la veille $ scope $ dans $ .debounce comme ceci:

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

Une autre solution consiste à ajouter une fonctionnalité de délai à la mise à jour du modèle. La directive simple semble faire un tour:

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

Usage:

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

Donc, vous utilisez simplement delayed-model à la place de ng-model et définissez le data-delay souhaité.

Démo: http://plnkr.co/edit/OmB4C3jtUD2Wjq5kzTSU?p=preview

3
dfsq

Pourquoi tout le monde veut-il utiliser la montre? Vous pouvez aussi utiliser une fonction:

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
NicoJuicy

Angular 1.3 aura ng-model-model options rebounce, mais jusque-là, vous devez utiliser une minuterie comme Josue Ibarra. Cependant, dans son code, il lance une minuterie à chaque pression sur une touche. En outre, il utilise setTimeout, quand dans Angular, il faut utiliser $ timeout ou utiliser $ apply à la fin de setTimeout.

0
F.A.

Je pense que le moyen le plus simple ici est de précharger le JSON ou de le charger une fois sur$dirty et ensuite la recherche par filtre se chargera du reste. Cela vous fera économiser les appels http supplémentaires et sa beaucoup plus rapidement avec des données préchargées. La mémoire va faire mal, mais ça vaut le coup.

0
NateNjugush

J'ai résolu ce problème avec une directive qui consiste essentiellement à lier le modèle réel sur un attribut spécial que je surveille dans la directive, puis à l'aide d'un service debounce, je mets à jour mon attribut il se lie à debounce-model au lieu de 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);
  }
};
});

Usage:

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

Et dans le contrôleur:

    $scope.search = "";
    $scope.$watch('search', function (newVal, oldVal) {
      if(newVal === oldVal){
        return;
      }else{ //do something meaningful }

Démo dans jsfiddle: http://jsfiddle.net/6K7Kd/37/ 

le service $ debounce peut être trouvé ici: http://jsfiddle.net/Warspawn/6K7Kd/

Inspiré par la directive finallyBindhttp://jsfiddle.net/fctZH/12/

0
Ofir D