จะทำให้ล่าช้าในการค้นหาทันใจของ 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 ไม่ได้มีขนาดใหญ่เพียง 300KB เท่านั้นฉันคิดว่าสิ่งที่ฉันต้องทำให้สำเร็จคือการหน่วงเวลาประมาณ 1 วินาทีในการค้นหาเพื่อรอให้ผู้ใช้พิมพ์เสร็จแทนที่จะทำการดำเนินการในแต่ละการกดแป้นพิมพ์ AngularJS ทำสิ่งนี้ภายในและหลังจากอ่านเอกสารและหัวข้ออื่น ๆ ที่นี่ฉันไม่พบคำตอบที่เฉพาะเจาะจง

ฉันขอขอบคุณพอยน์เตอร์ใด ๆ เกี่ยวกับวิธีที่ฉันสามารถหน่วงเวลาการค้นหาแบบทันที


1
คุณได้รับ json ทั้งหมดในแอป init ... จากนั้นตัวกรองการค้นหาของคุณจะไม่ได้รับข้อมูลในการพิมพ์ครั้งที่สอง ... เพราะเป็นการกรองรุ่นที่มีอยู่แล้ว ฉันถูกไหม?
Maksym

คำตอบด้านล่างนี้มีประโยชน์หรือไม่ ถ้าเป็นเช่นนั้นโปรดยอมรับคำตอบ ถ้าไม่แจ้งให้เราทราบและฉันจะชี้แจงเพิ่มเติม
เจสันเอเดน

เฮ้เจสันขอบคุณสำหรับคำตอบ ฉันพยายามเล่นด้วยโค้ดของคุณ แต่ไม่มีโชคการค้นหาหยุดทำงานพร้อมกันสำหรับฉัน
สมอง

ไม่เป็นไรมันเป็นสิ่งที่ไม่ดีของฉันฉันมองข้ามบางสิ่งบางอย่าง โซลูชันของคุณใช้งานได้จริง ขอบคุณ :)
สมอง

ดูคำตอบนี้ที่นี่ซึ่งมีคำสั่งที่ช่วยให้คุณสามารถหน่วงเวลาในการเปลี่ยนแปลง ng: stackoverflow.com/questions/21121460/…
Doug

คำตอบ:


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>

โปรดทราบว่า $ scope $ $ ดูng-modelจะไม่ทำงานภายในโมดัล bootstrap ของ angular-ui
Hendy Irawan

1
ฉันคิดว่ามันจะทำงานโดยไม่มีตัวแปร tempFilterText: $ scope $ watch ('searchText', function (val) {ถ้า (filterTextTimeout) $ timeout.cancel (filterTextTimeout); filterTextTimeout = $ timeout (function () {$ scope filterText = val;}, 250); // ล่าช้า 250 ms})
Jos Theeuwen

@JosTheeuwen มันแล้วก็ตัวแปรทั่วโลกซึ่งถือว่าการปฏิบัติที่ไม่ดีและไม่ได้รับอนุญาตในโหมดที่เข้มงวด
mb21

301

UPDATE

ตอนนี้มันง่ายกว่าที่เคย (Angular 1.3) เพียงเพิ่มตัวเลือก debounce ในโมเดล

<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

วิธีเก่า:

นี่เป็นอีกวิธีหนึ่งที่ไม่มีการพึ่งพานอกเหนือจากเชิงมุมเอง

คุณต้องตั้งค่าการหมดเวลาและเปรียบเทียบสตริงปัจจุบันของคุณกับรุ่นที่ผ่านมาหากทั้งคู่เหมือนกันจะทำการค้นหา

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

3
ไม่มีปัญหาที่การเปลี่ยนแปลงหลายรูปแบบอาจซ้อนกันทำให้เกิดคำขอซ้ำกันหรือไม่ ในคำตอบของ @ JasonAden เขาดูแลโดยยกเลิกเหตุการณ์ที่อยู่ในคิวก่อนหน้านี้
Blaskovicz

ในทางทฤษฎีหากแบบจำลองประสบการเปลี่ยนแปลง แต่ข้อมูลยังคงเหมือนเดิมมันจะทำให้เกิดการร้องขอหลายครั้ง ในทางปฏิบัติฉันไม่เคยเห็นมันเกิดขึ้น คุณสามารถเพิ่มการตั้งค่าสถานะเพื่อตรวจสอบกรณีขอบที่ถ้าคุณกังวล
Josue Alexander Ibarra

นี่เป็นตัวเลือกที่เหนือกว่าสำหรับเชิงมุม 1.3
Marcus W

คำเตือนที่นี่: หากคุณมีเหตุการณ์การกดปุ่มที่ส่งหรือทริกเกอร์มันจะทำเช่นนั้นโดยไม่มีค่ารุ่นล่าสุดเนื่องจากการผูกค่าจะถูกยกเลิก เช่นพิมพ์ 'foo' และกดปุ่มทันทีส่งคืนค่าจะยังคงเป็นสตริงว่าง
jbodily

34

ใน Angular 1.3 ฉันจะทำสิ่งนี้:

HTML:

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

ควบคุม:

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

โดยทั่วไปคุณกำลังบอกมุมให้ทำงานmyDebouncedFunction()เมื่อmsgตัวแปรขอบเขตเปลี่ยนแปลง แอตทริบิวต์ng-model-options="{debounce: 1000}"ทำให้แน่ใจว่าmsgสามารถอัปเดตได้เพียงครั้งเดียวเท่านั้น


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

ตอนนี้เราสามารถตั้งค่าตัวเลือก ng-model-options debounce ตามเวลาและเมื่อความพร่ามัวแบบจำเป็นต้องเปลี่ยนทันทีมิฉะนั้นการบันทึกจะมีค่าที่เก่ากว่าหากความล่าช้าไม่เสร็จสมบูรณ์


9

สำหรับผู้ที่ใช้ keyup / keydown ใน HTML markup สิ่งนี้ไม่ได้ใช้นาฬิกา

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

การอัพเดตโมเดล debounce / throttled สำหรับ angularjs: http://jsfiddle.net/lgersman/vPsGb/3/

ในกรณีของคุณไม่มีอะไรจะทำนอกจากการใช้คำสั่งในรหัส jsfiddle เช่นนี้:

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

มันเป็นรหัสชิ้นเล็ก ๆ ซึ่งประกอบด้วยคำสั่งเชิงมุมเดี่ยวที่ชื่อว่า "ng-ampere-debounce" โดยใช้http://benalman.com/projects/jquery-throttle-debounce-plugin/ซึ่งสามารถแนบกับองค์ประกอบ dom ใด ๆ คำสั่งจะเรียงลำดับตัวจัดการเหตุการณ์ที่แนบมาเพื่อให้สามารถควบคุมเวลาที่จะเค้นเหตุการณ์

คุณสามารถใช้มันเพื่อการควบคุมปริมาณ / debouncing * การปรับปรุงเชิงมุมของโมเดล * angler event handler 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

ผมเชื่อว่าวิธีที่ดีที่สุดในการแก้ปัญหานี้โดยใช้เบน Alman ของปลั๊กอินjQuery เค้น / debounce ในความคิดของฉันไม่จำเป็นต้องชะลอกิจกรรมของทุกฟิลด์ในฟอร์มของคุณ

เพียงแค่หุ้ม $ scope ของคุณ $ watch ฟังก์ชั่นการจัดการใน $ .debounce เช่นนี้

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

คุณจะต้องล้อมสิ่งนี้ด้วย $ scope $ ใช้
Aakil Fernandes

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-modeldata-delay

สาธิต: http://plnkr.co/edit/OmB4C3jtUD2Wjq5kzTSU?p=preview


เฮ้! คุณอธิบายmodel: '=delayedModel'ได้ไหมว่าทำงานอย่างไร หรือคุณสามารถชี้ให้ฉันไปที่ลิงก์ที่ฉันสามารถหาได้?
Akash Agrawal

@AkashAgrawal มันเป็นการผูกข้อมูลแบบสองทาง อ่านเกี่ยวกับที่นี่docs.angularjs.org/api/ng.$compile
dfsq

1
@dfsq ฉันใช้ ng-change และใช้เพื่อทริกเกอร์เมื่อใดก็ตามที่มีการเปลี่ยนแปลงข้อความ แต่ฉันไม่สามารถใช้งานได้เมื่อมีการกำหนดคำสั่ง element.on('change')ก่อให้เกิดการเบลอเท่านั้น (1) มีวิธีแก้ไขไหม? (2) วิธีการเรียกฟังก์ชั่นของตัวควบคุมเกี่ยวกับการเปลี่ยนข้อความ?
Vyas Rao

0

ฉันแก้ไขปัญหานี้ด้วยคำสั่งที่ทำในสิ่งที่เป็นพื้นฐานคือการผูก ng-model จริงบนคุณลักษณะพิเศษที่ฉันดูในคำสั่งจากนั้นใช้บริการ debounce ฉันอัปเดตแอตทริบิวต์ directive ของฉันดังนั้นผู้ใช้ดูตัวแปรที่ เขาผูกมัดกับ debounce-model แทน 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);
  }
};
});

การใช้งาน:

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

แรงบันดาลใจในที่สุดสั่ง directBind http://jsfiddle.net/fctZH/12/


0

Angular 1.3 จะมีการหักล้าง ng-model-options แต่ก่อนหน้านั้นคุณต้องใช้ตัวจับเวลาอย่างที่ Josue Ibarra กล่าว อย่างไรก็ตามในรหัสของเขาเขาเปิดตัวจับเวลาในทุกการกดปุ่ม นอกจากนี้เขากำลังใช้ setTimeout เมื่ออยู่ใน Angular ต้องใช้ $ timeout หรือใช้ $ 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.