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 KBだけではありません。私が達成する必要があるのは、キーストロークごとにアクションを実行するのではなく、ユーザーが入力を完了するのを待つために、検索に約1秒の遅延を設けることです。AngularJSはこれを内部で行います。ここでドキュメントやその他のトピックを読んだ後、特定の答えを見つけることができませんでした。

インスタント検索をどのように遅らせることができるかについてのアドバイスをいただければ幸いです。


1
initアプリですべてのjsonを取得しています...そして、入力時に検索フィルターが2度目にデータを取得していません...既存のモデルをフィルタリングしています。私は正しいですか?
Maksym

以下の答えはうまくいきましたか?もしそうなら、答えを受け入れてください。そうでない場合は、お知らせください。さらに明確にします。
Jason Aden

ジェイソンさん、回答ありがとうございます。私はあなたのコードをいじってみましたが、うまくいきませんでした。検索がまったく機能しなくなりました。
ブレインコム2013年

気にしないでください、私が見落としていたのは私の悪いことでした。あなたの解決策は確かに機能します。ありがとうございました:)
braincomb 2013年

あなたはNG-変化に遅れを置くことを可能にするディレクティブを提供し、ここでこの答えを見て、取る:stackoverflow.com/questions/21121460/...を
ダグ

回答:


121

(Angular 1.3ソリューションについては、以下の回答を参照してください。)

ここでの問題は、モデルが変更されるたびに検索が実行されることです。これは、入力に対するすべてのキーアップアクションです。

これを行うにはよりクリーンな方法がありますが、おそらく最も簡単な方法はバインディングを切り替えて、フィルターが動作するコントローラー内で$ 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>

angular-uiブートストラップのモーダルng-model内では、$ scope。$ watch は機能しないことに注意してください
Hendy Irawan 2014年

1
tempFilterText変数がなくても機能すると思います:$ scope。$ watch( 'searchText'、function(val){if(filterTextTimeout)$ timeout.cancel(filterTextTimeout); filterTextTimeout = $ timeout(function(){$ scope。 filterText = val;}、250); // 250 ms遅延})
Jos Theeuwen

@JosTheeuwenそれは、悪い習慣と考えられ、厳密モードでは許可されない単なるグローバル変数です
mb21 2015年

301

更新

これまでよりも簡単になりました(Angular 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

古い方法:

次のメソッドは、angular以外の依存関係がないものです。

タイムアウトを設定し、現在の文字列を過去のバージョンと比較する必要があります。両方が同じ場合、検索が実行されます。

$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_ 2013

3
複数のモデルの変更が重なり、リクエストが重複する問題はありませんか?@JasonAdenの回答では、以前にキューに入れられたイベントをキャンセルすることで対処しています。
Blaskovicz 14

理論的には、モデルに変更が生じてもデータが同じである場合、複数のリクエストが発生します。実際には、それが起こるのを見たことがありません。心配な場合は、エッジケースをチェックするフラグを追加できます。
Josue Alexander Ibarra 14

これは、角度1.3の優れた選択です
マーカスW

ここでの警告:送信またはトリガーするkeypressイベントがある場合、値のバインディングがデバウンスされるため、最新のモデル値なしで実行されます。たとえば、「foo」と入力しても、すぐにキーを押すと、値は空の文字列のままになります。
jbodily 2016年

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、1秒に1回しか更新できないことを確認します。


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

HTMLマークアップでkeyup / keydownを使用する人向け。これは時計を使用しません。

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

基本的に、http: //benalman.com/projects/jquery-throttle-debounce-plugin/を利用する「ng-ampere-debounce」という名前の単一の角度ディレクティブで構成される小さなコード片任意の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

この問題を解決する最善の方法は、Ben AlmanのプラグインjQueryスロットル/デバウンスを使用することだと思います。私の意見では、フォームのすべてのフィールドのイベントを遅らせる必要はありません。

$ scope。$ watch処理関数を次のように$ .debounceでラップするだけです。

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

これを$ scope。$ applyでラップする必要があります
Aakil Fernandes 2015

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'に働いているのか説明できますか?それとも私がそれを見つけることができるリンクに私を向けることができますか?
Akash Agrawal 2014年

@AkashAgrawalこれは双方向のデータバインディングです。これについては、docs.angularjs.org
api

1
@dfsq私はng-changeを使用していましたが、テキストに変更があるたびにトリガーされていました。ただし、ディレクティブが定義されている場合は使用できません。element.on('change')ぼかしでのみトリガーされます。(1)回避策はありますか?(2)テキストの変更時にコントローラーの関数を呼び出す方法は?
Vyas Rao

0

基本的には実際のng-modelをディレクティブで監視する特別な属性にバインドし、次にデバウンスサービスを使用してディレクティブ属性を更新するというディレクティブでこの問題を解決しました。彼はng-modelではなくdebounce-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);
  }
};
});

使用法:

<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が言ったようにタイマーを使用する必要があります。ただし、彼のコードでは、キーを押すたびにタイマーを起動します。また、Angularでは$ timeoutを使用するか、setTimeoutの最後に$ 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呼び出しが節約され、プリロードされたデータが大幅に高速化されます。メモリは傷つきますが、その価値はあります。

弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.