内容简介:Debouncing is the delay of a function/method execution or an action for a period of the specified time. During this specified time, calls to the method/function or action are collected and executes each one when the specified has elapsed.Suppose you have a
How to improve your Angular app’s performance with debouncing.
Debouncing is the delay of a function/method execution or an action for a period of the specified time. During this specified time, calls to the method/function or action are collected and executes each one when the specified has elapsed.
Suppose you have a blog website with a search bar where users can search for posts. When a user types in the name of a blog to search in the search bar, an HTTP request is sent to the web API to search for blog post names or mentions with the search tokens.
You will find out that the server will always be called on every single letter typed. For example, you want articles based on “Javascript” and “optimization”, then you typed “Javascript optimization”. For every single letter in “Javascript optimization”, the server will be called, that means the server will be called 23 times(the number of letters in “Javascript optimization” plus the space) just to search for blog posts with “Javascript optimization” keywords and *ngFor will trigger change detection run on the component 10 times.
The code in Angular will look like this:
@Component({ ..., template: ` <input type="text" placeholder="Search" (keyup)="search(evt)" <div *ngFor="let result of results$ | async"> //... </div> ` }) class SearchBarComponent { results$: Observable constructor(private httpClient: HttpClient) {} search(evt) { const searchText = evt.target.value this.results$ = this.httpClient.get("/api/search?q=" + searchText) } }
See, we bound the keyup event to the input#text to call the search method whenever we type on the text input. The search method extracts the data typed in the input#textbox and performs an HTTP GET request to "/api/search/"
via HttpClient, the result is assigned to results$ and is displayed on the DOM using *ngFor
and async
pipe.
Sending HTTP request for every keystroke could become very expensive. We have to delay the HTTP call to the server for a considerable amount of time before triggering a search for the currently typed word. This is called debouncing , it waits for a specified time before calling a function.
Let’s see the ways by which we can debounce the search method. First, is the use of the popular library RxJS operators: debounce and debounceTime.
Tip:as we all know, rewriting code is a recipe for bad code. Use tools like Bit ( Github ) to “harvest” reusable components from your codebase and share them on bit.dev . This way you and your team can easily find them, import them to any project and develop them further if needed. It’s a good way to guarantee high quality, scalable and maintainable code.
1. Using RxJS
debounce
We will use RxJS debounce
operator to delay calls to the search method
debounce emits a value form the source Observable only after a particular life span determined by another Observable has passed without another source emission.
@Component({ ..., template: ` <input type="text" placeholder="Search" (keyup)="search(evt)" <div *ngFor="let result of results$ | async"> //... </div> ` }) class SearchBarComponent implements OnInit { results$: Observable subject = new Subject() constructor(private httpClient: HttpClient) {} ngOnInit() { this.results$ = this.subject.pipe( debounce(() => Rx.Observable.interval(1000)), map(searchText => this.httpClient.get("/api/search?q=" + searchText)) ) } search(evt) { const searchText = evt.target.value // emits the `searchText` into the stream. This will cause the operators in its pipe function (defined in the ngOnInit method) to be run. `debounce` runs and then `map`. If the time interval of 1 sec in debounce hasn't elapsed, map will not be called, thereby saving the server from being called. this.subject.next(searchText) } }
We added a Subject (a type of Observable), this is to form a gateway in which the text search will pass before hitting the HttpClient. We used the pipe function in the Subject to add operators: debounce and map.
We set the debounce
to be 1 sec (1000ms), debounce
depends on external source Observable to set the time debounce would have to delay emission, we used the interval function to set the time. The map
has a callback function that will perform the HTTP GET request to the server. With this, we have added a delay to our search mechanism.
debounceTime
We can write cleaner code with debounceTime , this operator doesn’t need time to be set by a source Observable, here we pass the time delay to the debounceTime operator.
@Component({ ..., template: ` <input type="text" placeholder="Search" (keyup)="search(evt)" <div *ngFor="let result of results$ | async"> //... </div> ` }) class SearchBarComponent implements OnInit { results$: Observable subject = new Subject() constructor(private httpClient: HttpClient) {} ngOnInit() { this.results$ = this.subject.pipe( debounceTime(1000), map(searchText => this.httpClient.get("/api/search?q=" + searchText)) ) } search(evt) { const searchText = evt.target.value // emits the `searchText` into the stream. This will cause the operators in its pipe function (defined in the ngOnInit method) to be run. `debounceTime` runs and then `map`. If the time interval of 1 sec in debounceTime hasn’t elapsed, map will not be called, thereby saving the server from being called. this.subject.next(searchText) } }
Same but simpler.
2. Using lodash/underscore debounce method
The lodash and underscore utility libraries export the debounce function that we can use to debounce methods/functions execution.
underscore
Let’s see how we can use the debounce in the underscore library to debounce our search function:
// ... import * as _ from 'underscore';@Component({ ..., template: ` <input type="text" placeholder="Search" (keyup)="search(evt)" <div *ngFor="let result of results$ | async"> //... </div> ` }) class SearchBarComponent { results$: Observable constructor(private httpClient: HttpClient) { this.search = _.debounce(this.search, 1000) } search(evt) { const searchText = evt.target.value this.results$ = this.httpClient.get("/api/search?q=" + searchText) } }
We imported all the exported functions from underscore as _
. Then, in the constructor, we called the _.debounce
function passing in our search method and delay time of 1 sec, and re-assigning the returned debounced-version of this.search to the search method. With this, the search method has become debounced, it will not call the server until 1 sec has elapsed.
Let’s repeat the same with lodash
lodash
// ... import { debounce } from 'lodash';@Component({ ..., template: ` <input type="text" placeholder="Search" (keyup)="search(evt)" <div *ngFor="let result of results$ | async"> //... </div> ` }) class SearchBarComponent { results$: Observable constructor(private httpClient: HttpClient) { this.search = debounce(this.search, 1000) } search(evt) { const searchText = evt.target.value this.results$ = this.httpClient.get("/api/search?q=" + searchText) } }
Same as what we did using underscore.
3. Using decorators
We can leverage decorators to debounce methods in our Angular components. Angular already made heavy use of decorators, so using decorators for debouncing methods would not be off.
import { debounce } from 'lodash';/** * Debounce a method */ function Debounce(ms) { return function(target: any, key: any, descriptor: any) { const oldFunc = descriptor.value const newFunc = debounce(oldFunc, ms) descriptor.value = function() { return newFunc.apply(this, arguments) } } }import * as _ from 'underscore';/** * Debounce a method */ function Debounce(ms) { return function(target: any, key: any, descriptor: any) { const oldFunc = descriptor.value const newFunc = _.debounce(oldFunc, ms) descriptor.value = function() { return newFunc.apply(this, arguments) } } }
We can then decorate the search
method with the Debounce decorator:
@Component({ ..., template: ` <input type="text" placeholder="Search" (keyup)="search(evt)" <div *ngFor="let result of results$ | async"> //... </div> ` }) class SearchBarComponent { results$: Observable constructor(private httpClient: HttpClient) { } @Debounce(1000) search(evt) { const searchText = evt.target.value this.results$ = this.httpClient.get("/api/search?q=" + searchText) } }
Simple
Conclusion
We have seen ways by which we can debounce or delay HTTP requests in an Angular app. RxJS, lodash and underscore libraries provide a great and safe way we can use to prevent unwanted HTTP requests from our Angular app.
Delaying HTTP requests shuts out unnecessary server requests, preventing a function/method/action both client- and server-side from being called over and over rapidly.
If you have any questions regarding this or anything I should add, correct or remove, feel free to comment, email or DM me.
Thanks !!!
以上就是本文的全部内容,希望本文的内容对大家的学习或者工作能带来一定的帮助,也希望大家多多支持 码农网
猜你喜欢:本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们。