📜 ⬆️ ⬇️

AngularJS Filter Cache with Lo-Dash

AngularJS as a fast tiger ^. ^

I am writing a diploma where I solve one of the tasks - the implementation of an anonymous fast web chat. Fast in every sense - loading, application work, use (authorization off). Choice stopped on a bunch: Node.js framework SocketStream and AngularJS on the client side. In the process, I ran into a problem - repeated calculations made by filters on the same model. Details of the problem and the solution under the cut.



Reader Level:
AngularJS: medium (creating filters)
Lo-Dash: "saw-felt"

Go directly to the solution
')

Problem in details


We have a large array, with which our application constantly works by manipulating its elements. You must apply a complex filter to the array, for example, sorting by date and selecting elements with a certain property. Let's transfer this problem to the application area - a simplified version of my chat. The elements of the array are chats (rooms / circles) that contain messages. Chat has the following structure:

{ id: 'rE4aA', title: ' ', online: 3, recent: 0, //    messages: [] //  } 

I want to display on the page using the ngRepeat {N} directive the number of chats (depending on the screen size). And I want to display a context menu that appears when you click the right mouse button on the title of any of the chats and allows you to move the selected chat to the place of another. This is how it looks like:

Right click on the chat header
Right click on the chat header

Backlight chat, in place of which we move
Backlight chat, in place of which we move

Such functionality can be implemented by creating two lists with the ngRepeat directive and applying a filter. For chats, the filter should be able to sort by the number of new messages (the recent property) and reduce the number of elements (chats) to the number {N} which is calculated from the size of the browser window. For the context menu, the same filter excludes the current element (the chat on which header was clicked).

Filter Code:
 angular.module('app') .filter('opened', ['$rootScope', function($s){ return function(o){ console.log('  «opened»'); var count = $s.count; //  ,  {N} return _(o) //    Lo-Dash .sortBy('recent') //      .reverse() //  (   ) .first(count) //   {N}  .value() //   } }]); 

By applying this filter to the array argument passed to each ngRepeat directive, ngRepeat see that in the console the message “The“ opened filter ”has been shown is shown twice. This means that half of the resources were wasted by the filter. Such convenience as the context menu doubled the rendering time of the actual state of the application. And if I continue to add functionality using the same data with filters, the situation will get even worse.


Solution to the problem


The solution is to create a function that returns a filtered array. This function is used instead of the original array without using the native filter provider. The function is wrapped in the Lo-Dash memoize property, which implements the caching functionality. Below I will explain how memoize works and give an example implementation.


Lo-Dash memoize property


Arguments:
  1. - (required) - the cached result of this function returns the memoize
  2. - (optional) - the result of the function is the cache key (checks for uniqueness)

_.memoize (fn, [fn]) returns the function that, when first called, performs the calculation, remembers the result (creates the cache) and returns it. On subsequent calls, returns a cache. All this is true for a single cache key.

The cache key is determined by the result of the function, which is passed to the second argument. By default (if the second argument is not specified), memoize uses the first argument as the cache key.


In a vivid example


At the end of the short listing there will be a link to the demonstration, but I suggest paying attention to the comments in the code.

Create a simple controller with one “form” glued together object:
 function MyController($scope){ $scope.form = { input: {key:'', val:''}, //       array: [ {key:'pear', val:''}, //  {key:'melon', val:''}, {key:'ananas', val:''}, {key:'cherry', val:''} ], order: 'key', //      key (2  key/val) check: false, //      .   (2  — true/false) add: function(){ //          this.array.push(angular.copy(this.input)); this.filtered.cache = {} //    }, filtered: _.memoize( //   function(){ console.log('  : ' + this.order + '  ' + this.check); return _.sortBy(this.array, this.order) }, function(){ //  - //      return [this.order, this.check] } ) } } 

Some HTML:
 <form name="myform" ng-app ng-controller="MyController"> <input type="text" required ng-model="form.input.key" placeholder="key"> <input type="text" required ng-model="form.input.val" placeholder="val"> <button ng-disabled="!myform.$valid" ng-click="form.add()"></button><br><br> <fieldset> <legend>   : <select ng-model="form.order" ng-options="p for p in ['key', 'val']"></select> </legend> <div ng-repeat="el in form.filtered()"> {{el.key}} — "{{el.val}}" </div><br> <label> <input type="checkbox" ng-model="form.check">   -   </label><hr> <pre>{{form.filtered()|json}}</pre> </fieldset> </form> 

We go to watch result on jsFiddle . Open the console with the combination of Ctrl + Shift + J (relevant for the Chrome browser). We try to switch sorting and pull the flag. In the console, we see a maximum of 4 starts of the filter function (for each of the states). Adding a new element to the array - reset the cache and again verify the correct operation of this solution.

Thanks to the wonderful Lo-Dash library, and specifically the memoize property, I was seriously able to increase the speed of the AngularJS application. If I applied the native filter, since the moment the application was launched, the filter worked 8 times against 1 (solution with memoize).

From the community I look forward to constructive criticism and thoughts about the methods of "pumping" the native filter.

PS: I thank UFO for the invitation to Habr.

Source: https://habr.com/ru/post/200130/


All Articles