Wie verzögere ich die sofortige Suche nach AngularJS?

147

Ich habe ein Leistungsproblem, das ich anscheinend nicht ansprechen kann. Ich habe eine sofortige Suche, aber sie ist etwas verzögert, da sie bei jeder Suche beginnt 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>

Die JSON-Daten sind nicht einmal so groß, nur 300 KB. Ich denke, ich muss eine Verzögerung von ~ 1 Sekunde für die Suche festlegen, um zu warten, bis der Benutzer die Eingabe beendet hat, anstatt die Aktion bei jedem Tastendruck auszuführen. AngularJS macht das intern und nachdem ich hier Dokumente und andere Themen gelesen hatte, konnte ich keine spezifische Antwort finden.

Ich würde mich über Hinweise freuen, wie ich die sofortige Suche verzögern kann.

Gehirnkamm
quelle
1
Sie erhalten alle JSON in der Init-App ... und dann sucht Ihr Suchfilter die Daten beim zweiten Eingeben nicht zum zweiten Mal ... er filtert bereits vorhandenes Modell. Hab ich recht?
Maksym
Hat die Antwort unten geklappt? Wenn ja, akzeptieren Sie bitte die Antwort. Wenn nicht, lassen Sie es mich wissen und ich werde es weiter klären.
Jason Aden
Hey Jason, danke für die Antwort. Ich habe versucht, mit Ihrem Code herumzuspielen, aber kein Glück, die Suche funktioniert für mich nicht mehr.
Braincomb
Egal, es war mein Schlimmes, dass ich etwas übersehen habe. Ihre Lösung funktioniert in der Tat. Vielen Dank :)
Braincomb
Schauen Sie sich diese Antwort hier an, die eine Anweisung enthält, mit der Sie ng-change verzögern können: stackoverflow.com/questions/21121460/…
Doug

Antworten:

121

(Eine Angular 1.3-Lösung finden Sie in der Antwort unten.)

Das Problem hierbei ist, dass die Suche jedes Mal ausgeführt wird, wenn sich das Modell ändert. Dies ist jede Keyup-Aktion für eine Eingabe.

Es gibt sauberere Möglichkeiten, dies zu tun, aber wahrscheinlich ist es am einfachsten, die Bindung so zu ändern, dass in Ihrem Controller eine $ scope-Eigenschaft definiert ist, mit der Ihr Filter arbeitet. Auf diese Weise können Sie steuern, wie oft diese $ scope-Variable aktualisiert wird. Etwas wie das:

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>
Jason Aden
quelle
Beachten Sie, dass $ scope. $ Watch on ng-modelnicht im modalen Winkel-UI-Bootstrap
Hendy Irawan
1
Ich denke, es wird auch ohne die Variable tempFilterText funktionieren: $ scope. $ Watch ('searchText', Funktion (val) {if (filterTextTimeout) $ timeout.cancel (filterTextTimeout); filterTextTimeout = $ timeout (function () {$ scope. filterText = val;}, 250); // Verzögerung 250 ms})
Jos Theeuwen
@JosTheeuwen es ist dann einfach eine globale Variable, die als schlechte Praxis angesehen wird und im strengen Modus nicht erlaubt ist .
mb21
301

AKTUALISIEREN

Jetzt ist es einfacher als je zuvor (Angular 1.3). Fügen Sie dem Modell einfach eine Entprellungsoption hinzu.

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

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

Dokumentation zu ngModelOptions:
https://docs.angularjs.org/api/ng/directive/ngModelOptions

Alte Methode:

Hier ist eine andere Methode ohne Abhängigkeiten, die über den Winkel hinausgehen.

Sie müssen ein Zeitlimit festlegen und Ihre aktuelle Zeichenfolge mit der vorherigen Version vergleichen. Wenn beide identisch sind, wird die Suche ausgeführt.

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

und das geht in deine Sicht:

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

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

Der obligatorische Plunker: http://plnkr.co/dAPmwf

Josue Alexander Ibarra
quelle
2
Für mich ist es eine viel verständlichere Antwort als akzeptiert :) Danke!
OZ_
3
Gibt es nicht ein Problem, bei dem sich mehrere Modelländerungen stapeln und so doppelte Anforderungen verursachen könnten? In der Antwort von @ JasonAden kümmert er sich darum, indem er zuvor in der Warteschlange befindliche Ereignisse abbricht.
Blaskovicz
Wenn das Modell eine Änderung erfährt, die Daten jedoch gleich bleiben, würde dies theoretisch zu mehreren Anforderungen führen. In der Praxis habe ich das noch nie gesehen. Sie können eine Flagge hinzufügen, um nach diesem Randfall zu suchen, wenn Sie sich Sorgen machen.
Josue Alexander Ibarra
Dies ist bei weitem die
Marcus W
Warnung hier: Wenn Sie ein Tastendruckereignis haben, das gesendet oder ausgelöst wird, geschieht dies ohne den neuesten Modellwert, da die Wertebindung entprellt wird. Geben Sie z. B. 'foo' ein und bei einem sofortigen Tastendruck ist der Wert immer noch eine leere Zeichenfolge.
Jbodily
34

In Angular 1.3 würde ich Folgendes tun:

HTML:

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

Regler:

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

Grundsätzlich weisen Sie Angular an, ausgeführt zu werden myDebouncedFunction(), wenn sich die msgBereichsvariable ändert. Das Attribut ng-model-options="{debounce: 1000}"stellt sicher, dass msgnur einmal pro Sekunde aktualisiert werden kann.

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

Jetzt können wir ng-model-options mit der Zeit entprellen lassen und wenn Unschärfe, muss das Modell sofort geändert werden, sonst hat es beim Speichern einen älteren Wert, wenn die Verzögerung nicht abgeschlossen ist.

Ali Adravi
quelle
9

Für diejenigen, die Keyup / Keydown im HTML-Markup verwenden. Dies verwendet keine Uhr.

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...">
Vinoth
quelle
6

Entprellte / gedrosselte Modellaktualisierungen für AngularJS: http://jsfiddle.net/lgersman/vPsGb/3/

In Ihrem Fall gibt es nichts weiter zu tun, als die Direktive im jsfiddle-Code wie folgt zu verwenden:

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

Es ist im Grunde ein kleiner Code, der aus einer einzelnen Winkelanweisung mit dem Namen "ng-ampere-debounce" besteht, die http://benalman.com/projects/jquery-throttle-debounce-plugin/ verwendet und an jedes dom-Element angehängt werden kann. Die Direktive ordnet die angehängten Ereignishandler neu an, damit sie steuern kann, wann Ereignisse gedrosselt werden sollen.

Sie können es zum Drosseln / Entprellen verwenden * Modellwinkelaktualisierungen * Winkelereignishandler ng- [Ereignis] * jquery Ereignishandler

Schauen Sie mal rein : http://jsfiddle.net/lgersman/vPsGb/3/

Die Richtlinie wird Teil des Orangevolt Ampere-Frameworks ( https://github.com/lgersman/jquery.orangevolt-ampere ) sein.

Lgersman
quelle
6

Nur für Benutzer, die hier umgeleitet werden:

Wie in eingeführt Angular 1.3, können Sie das Attribut ng-model-options verwenden :

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

Ich glaube, dass der beste Weg, um dieses Problem zu lösen, die Verwendung von Ben Almans Plugin jQuery Throttle / Debounce ist . Meiner Meinung nach besteht keine Notwendigkeit, die Ereignisse jedes einzelnen Feldes in Ihrem Formular zu verzögern.

Wickeln Sie einfach Ihre $ scope. $ Watch-Handling-Funktion in $ .debounce wie folgt ein:

$scope.$watch("searchText", $.debounce(1000, function() {
    console.log($scope.searchText);
}), true);
Daniel Popov
quelle
Sie müssen dies in einen $ Bereich
einschließen
3

Eine andere Lösung besteht darin, der Modellaktualisierung eine Verzögerungsfunktion hinzuzufügen. Die einfache Anweisung scheint einen Trick zu machen:

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

Verwendung:

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

Sie verwenden also nur delayed-modelanstelle von ng-modelund definieren die gewünschten data-delay.

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

dfsq
quelle
Hallo! Kannst du erklären, wie model: '=delayedModel'es funktioniert? Oder können Sie mich auf einen Link verweisen, wo ich ihn finden kann?
Akash Agrawal
@AkashAgrawal Es ist eine bidirektionale Datenbindung. Lesen Sie hier darüber docs.angularjs.org/api/ng.$compile
dfsq
1
@dfsq Ich habe ng-change verwendet und es wurde ausgelöst, wenn sich der Text ändert. Aber ich kann es nicht verwenden, wenn eine Direktive definiert ist. element.on('change')wird nur bei Unschärfe ausgelöst. (1) Gibt es eine Problemumgehung? (2) Wie rufe ich eine Funktion des Controllers bei einer Textänderung auf?
Vyas Rao
0

Ich habe dieses Problem mit einer Direktive gelöst, die im Grunde genommen das echte ng-Modell an ein spezielles Attribut bindet, das ich in der Direktive beobachte, und dann mithilfe eines Debounce-Dienstes mein Direktivenattribut aktualisiere, damit der Benutzer die Variable überwacht, die er bindet an das Entprellungsmodell anstelle des ng-Modells.

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

Verwendung:

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

Und in der Steuerung:

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

Demo in jsfiddle: http://jsfiddle.net/6K7Kd/37/

Den $ debounce-Service finden Sie hier: http://jsfiddle.net/Warspawn/6K7Kd/

Inspiriert von der finallyBind-Richtlinie http://jsfiddle.net/fctZH/12/

Ofir D.
quelle
0

Angular 1.3 wird ng-model-options debounce haben, aber bis dahin müssen Sie einen Timer verwenden, wie Josue Ibarra sagte. In seinem Code startet er jedoch bei jedem Tastendruck einen Timer. Außerdem verwendet er setTimeout, wenn in Angular $ timeout oder $ apply am Ende von setTimeout verwendet werden muss.

FA
quelle
0

Warum will jeder die Uhr benutzen? Sie können auch eine Funktion verwenden:

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);
}; 
NicoJuicy
quelle
0

Ich denke, der einfachste Weg hier ist, den JSON vorzuladen oder einmal zu laden, $dirtyund dann kümmert sich die Filtersuche um den Rest. Dies erspart Ihnen die zusätzlichen http-Aufrufe und ist mit vorinstallierten Daten viel schneller. Das Gedächtnis wird weh tun, aber es lohnt sich.

NateNjugush
quelle