Sticky row in md-virtual-repeat

One of the major problems with big data display is the amount of dom elements needed in order to display the data.  In the case of grid or rows of data, the virtual repeat (or infinite scroll) are an elegant and very useful solution.  Luckily, angular-material-design have supplied us with this tool out of the box:

Notice that the dom elements aren’t really there as long as they are not in the screen.  That’s really useful when you have 100 rows or more, but showing only 10 for instance… In this example the ng-repeat “eats” 20000 rows without breaking sweat. Pretty neat.

Disclaimer: In the following development I’m going to use jQuery instead of the jqLite that comes with angularjs for convinience. I’m also not doing this 100% best practice as I would have probably done in a none-demo code.  Feel free to use this code as a basis for improvement.

Now, assuming you want to add an option for the user to select a row.  Here’s one way to do it: create a new directive and add it to the md-virtual-repeat-container element.  This directive exposes a function that can be used by the elements of the virtual repeat: “selectRow(item)”.  All it does is adds a select class to the clicked item and removes the select class from every other element in the md-virtual-repeat.  

Best practice notes: I’m using ng-class in order to set the select class. You are better off exposing API methods rather than creating watchers. It is also better to use direct dom changes in order to add a class to a row in an ng-repeat, since every ng-class means another watcher. Times 20,000… (well, with virtual repeat it’s not so bad, but still, why add 10 watchers when you can use 0?

Works fine, and you can play with it – select, scroll and see your selection and do whatever you want with it.

As usual, real time update is right behind the corner to make it more complex.  In the following demo, you have the same setup, only you can start asking data from the server. The data is mocked and comes in bunches of 1 to 10 updates per half a second (relatively slow rate for us, right?).  Anyway, try to select a row,  and click the “Inject Data” button.

See what happens to the selection? The user loses focus on the selection.

How do we solve this? Here’s the trick: the actual scroll remains constant. What changes is the location of the selected row in regards to the top of the “real” div (set by the class md-virtual-repeat-sizer).  For this we can actually use the number of elements in our data.  Let’s compare indices of before and after the addition of data and then apply the difference times rowHeight to the scrollTop of the view port.

What was added in the solution below are a few things:

  1. A method that was exposed to the parent controller (scope.ctrl.dataBinding) that was evoked at the time of the actual data change (instead of the mock up it could have been an ajax call or socket event).
  2. I’ve caught the relevant DOM elements of the virtual-repeat directive using a $watch (which I’ve destroyed once I’ve caught the elements I needed).
  3. In the binding itself I commence an $evalAsync, which makes sure my code runs at the end of the digest cycle, and then I do the calculations – get the height of a row, calculate the difference between current position and last position, and then change the scrollTop of the viewPort. (the first child of the mdVirtualRepeat).

Here’s the full solution:



2 thoughts on “Sticky row in md-virtual-repeat

Leave a Reply