Bagaimana cara menunda pencarian instan AngularJS?

147

Saya memiliki masalah kinerja yang sepertinya tidak bisa saya tangani. Saya memiliki pencarian instan tetapi agak lambat, karena mulai mencari pada masing-masing 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>

Data JSON bahkan tidak sebesar itu, hanya 300KB, saya pikir apa yang harus saya selesaikan adalah menunda ~ 1 detik pada pencarian untuk menunggu pengguna selesai mengetik, alih-alih melakukan tindakan pada setiap penekanan tombol. AngularJS melakukan ini secara internal, dan setelah membaca dokumen dan topik lain di sini saya tidak dapat menemukan jawaban spesifik.

Saya akan menghargai petunjuk tentang bagaimana saya dapat menunda pencarian instan.

braincomb
sumber
1
Anda mendapatkan semua json di aplikasi init ... dan kemudian filter pencarian Anda tidak mendapatkan data kedua kalinya pada pengetikan ... itu memfilter model yang sudah ada. Apakah saya benar?
Maksym
Apakah jawaban di bawah ini berhasil? Jika demikian, harap terima jawabannya. Jika tidak, beri tahu saya dan saya akan mengklarifikasi lebih lanjut.
Jason Aden
Hai Jason, terima kasih atas tanggapannya. Saya mencoba untuk bermain-main dengan kode Anda tetapi tidak berhasil, pencarian berhenti bekerja sama sekali untuk saya.
braincomb
Bagaimanapun, itu adalah kesalahanku aku mengabaikan sesuatu. Solusi Anda memang berhasil. Terima kasih :)
braincomb
Lihatlah jawaban ini di sini, yang memberikan arahan yang memungkinkan Anda untuk menunda perubahan-t: stackoverflow.com/questions/21121460/…
Doug

Jawaban:

121

(Lihat jawaban di bawah untuk solusi 1.3 Sudut.)

Masalahnya di sini adalah bahwa pencarian akan mengeksekusi setiap kali model berubah, yang merupakan setiap tindakan keyup pada input.

Akan ada cara yang lebih bersih untuk melakukan ini, tetapi mungkin cara termudah adalah dengan mengganti ikatan sehingga Anda memiliki properti $ scope didefinisikan di dalam Controller Anda di mana filter Anda beroperasi. Dengan begitu Anda dapat mengontrol seberapa sering variabel $ scope diperbarui. Sesuatu seperti ini:

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
sumber
Perhatikan bahwa $ scope. $ Watch on a ng-modeltidak akan berfungsi di dalam modal angular-ui bootstrap
Hendy Irawan
1
Saya pikir itu juga akan berfungsi tanpa variabel tempFilterText: $ scope. $ Watch ('searchText', function (val) {if (filterTextTimeout) $ timeout.cancel (filterTextTimeout); filterTextTimeout = $ timeout (function () {$ scope. filterText = val;}, 250); // tunda 250 ms})
Jos Theeuwen
@ JosephTheeuwen, maka itu hanyalah variabel global yang dianggap praktik buruk dan tidak diizinkan dalam mode ketat .
mb21
301

MEMPERBARUI

Sekarang lebih mudah dari sebelumnya (Angular 1.3), cukup tambahkan opsi debounce pada model.

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

Pembaruan yang diperbarui:
http://plnkr.co/edit/4V13gK

Dokumentasi tentang ngModelOptions:
https://docs.angularjs.org/api/ng/directive/ngModelOptions

Metode lama:

Inilah metode lain tanpa ketergantungan di luar sudut itu sendiri.

Anda perlu mengatur batas waktu dan membandingkan string Anda saat ini dengan versi yang lalu, jika keduanya sama maka melakukan pencarian.

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

dan ini masuk ke dalam pandangan Anda:

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

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

Plunker wajib: http://plnkr.co/dAPmwf

Josue Alexander Ibarra
sumber
2
Bagi saya itu jawaban yang jauh lebih masuk akal daripada diterima :) Terima kasih!
OZ_
3
Apakah tidak ada masalah di mana beberapa perubahan model bisa menumpuk, sehingga menyebabkan permintaan duplikat? Dalam jawaban @ JasonAden, ia menanganinya dengan membatalkan acara yang sebelumnya sudah antri.
Blaskovicz
Secara teori, jika model mengalami perubahan, tetapi datanya tetap sama, itu akan menyebabkan banyak permintaan. Dalam praktiknya, saya belum pernah melihatnya terjadi. Anda dapat menambahkan bendera untuk memeriksa kasing tepi jika Anda khawatir.
Josue Alexander Ibarra
Sejauh ini, ini adalah pilihan terbaik untuk sudut 1.3
Marcus W
Peringatan di sini: Jika Anda memiliki acara penekanan tombol yang mengirim atau memicu, itu akan melakukannya tanpa nilai model terbaru karena nilai mengikat didebok. mis. ketik 'foo' dan pada saat tombol ditekan kembali, nilainya masih berupa string kosong.
jbodily
34

Dalam Angular 1.3 saya akan melakukan ini:

HTML:

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

Pengendali:

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

Pada dasarnya Anda mengatakan sudut untuk dijalankan myDebouncedFunction(), ketika msgvariabel ruang lingkup berubah. Atribut ng-model-options="{debounce: 1000}"memastikan bahwa msghanya dapat memperbarui satu detik sekali.

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

Sekarang kita dapat mengatur opsi ng-model-opsi dengan waktu dan ketika blur, model harus segera diganti jika tidak menyimpannya akan memiliki nilai yang lebih tua jika penundaan tidak selesai.

Ali Adravi
sumber
9

Bagi mereka yang menggunakan keyup / keydown di markup HTML. Ini tidak menggunakan arloji.

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
sumber
6

Pembaruan model yang didebitkan / dibatasi untuk angularjs: http://jsfiddle.net/lgersman/vPsGb/3/

Dalam kasus Anda, tidak ada yang bisa dilakukan selain menggunakan arahan dalam kode jsfiddle seperti ini:

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

Ini pada dasarnya sepotong kecil kode yang terdiri dari satu directive angular directive bernama "ng-ampere-debounce" memanfaatkan http://benalman.com/projects/jquery-throttle-debounce-plugin/ yang dapat dilampirkan ke elemen dom apa pun. Arahan mengatur ulang penangan acara terlampir sehingga dapat mengontrol kapan harus mencekik acara.

Anda dapat menggunakannya untuk pembatasan / debouncing * model pembaruan sudut * pengatur acara sudut - [acara] * penangan acara jquery

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

Arahan akan menjadi bagian dari kerangka kerja Orangevolt Ampere ( https://github.com/lgersman/jquery.orangevolt-ampere ).

Lgersman
sumber
6

Hanya untuk pengguna yang dialihkan ke sini:

Seperti yang diperkenalkan di Angular 1.3Anda dapat menggunakan atribut ng-model-options :

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

Saya percaya bahwa cara terbaik untuk menyelesaikan masalah ini adalah dengan menggunakan plugin jQuery throttle / debounce dari Ben Alman . Menurut pendapat saya, tidak perlu menunda acara dari setiap bidang dalam formulir Anda.

Hanya bungkus $ scope Anda. $ Watch handling function dalam $ .debounce seperti ini:

$scope.$watch("searchText", $.debounce(1000, function() {
    console.log($scope.searchText);
}), true);
Daniel Popov
sumber
Anda harus membungkus ini dalam $ scope. $
Apply
3

Solusi lain adalah menambahkan fungsionalitas penundaan ke pembaruan model. Arahan sederhana tampaknya melakukan trik:

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

Pemakaian:

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

Jadi Anda hanya menggunakan delayed-modeldi tempat ng-modeldan menentukan diinginkan data-delay.

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

dfsq
sumber
Hei! dapatkah Anda menjelaskan bagaimana cara model: '=delayedModel'kerjanya? Atau bisakah Anda mengarahkan saya ke tautan di mana saya dapat menemukannya?
Akash Agrawal
@AkashAgrawal Ini adalah pengikatan data dua arah. Baca tentang ini di sini docs.angularjs.org/api/ng.$compile
dfsq
1
@dfsq Saya menggunakan ng-change dan digunakan untuk memicu setiap kali ada perubahan pada teks. Tapi saya tidak bisa menggunakannya ketika arahan didefinisikan. element.on('change')hanya memicu blur. (1) Apakah ada pekerjaan? (2) bagaimana cara memanggil fungsi controller pada perubahan teks?
Vyas Rao
0

Saya memecahkan masalah ini dengan arahan yang pada dasarnya apa yang dilakukannya adalah untuk mengikat ng-model nyata pada atribut khusus yang saya tonton dalam arahan, kemudian menggunakan layanan debounce saya memperbarui atribut direktif saya, sehingga pengguna menonton pada variabel yang ia mengikat ke model-debo alih-alih model-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);
  }
};
});

Pemakaian:

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

Dan di controller:

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

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

layanan $ debounce dapat ditemukan di sini: http://jsfiddle.net/Warspawn/6K7Kd/

Terinspirasi oleh akhirnyaBind direktif http://jsfiddle.net/fctZH/12/

Ofir D
sumber
0

Angular 1.3 akan memiliki opsi ng-model-opsi, tetapi sampai saat itu, Anda harus menggunakan timer seperti kata Josue Ibarra. Namun, dalam kodenya ia meluncurkan timer pada setiap tombol yang ditekan. Juga, dia menggunakan setTimeout, ketika di Angular kita harus menggunakan $ timeout atau menggunakan $ apply di akhir setTimeout.

FA
sumber
0

Mengapa semua orang ingin menggunakan arloji? Anda juga bisa menggunakan fungsi:

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
sumber
0

Saya pikir cara termudah di sini adalah untuk memuat ulang json atau memuatnya sekali $dirtydan kemudian pencarian filter akan mengurus sisanya. Ini akan menghemat panggilan http ekstra dan jauh lebih cepat dengan data yang dimuat sebelumnya. Ingatan akan sakit, tetapi itu sangat berharga.

NateNjugush
sumber