Skip to content

Instant Listview Bindings

nicknaky edited this page Oct 17, 2015 · 2 revisions

This tutorial will explain the steps and inner workings of how we implement our list views and bind them with inputs and other various services such as Geolocation and/or User updates to the server.

Let's start with our University list view in AddUniversityCtrl.js:

Here we have two obvious bindings to this page: the text input that allows users to search for their school, and then the location toggle on the right of the input that will sort the list of schools based on the distance to the user. The HTML is rather straightforward, with a simple <ul> tag wrapped around an <li> with ng-repeat.

However we will then wrap the entire list in a custom directive which will handle all the bindings and DOM manipulations. This allows us to keep things much more modularized and with less dependency on manipulating the DOM through the controller, which is not recommended.

The bind-list attribute binds the entire div container with our directive.

We pass our original static list data into the source attribute and then the dynamic list (the one tied to ng-repeat). We call the latter dynamic because when we bind our fastMatcher library to the input, we will feed in our static list data (the original list) and then it returns the results of the filtered data, which is assigned to the ng-repeat list, thus the list will frequently change based on the user's interactions.

Notice how we instantiate a watcher that automatically handles all the querying every time the value in the input field is changed. This provides several benefits over a keyup listener: We have one encapsulated listener that handles the search, leaving keyup for other things we may decide to implement later one. And by listening to the value change we automatically handle all the various edge cases that may arise when the user copy/pastes, selects in the middle, custom keyboards, etc.

The Problem:

This is all fairly straightforward and generally works out of the box. However issues arise when we handle remote events that manipulate our source data. Some examples include automatically fetching GPS coordinates and sorting the list on App start (for Android devices), or when another list view comes into the picture such as the User Majors/Courses list views where the list items are constantly being transferred between one list to another as users select or remove their items.

Angular provides us with their $scope objects which handle all the updates to the DOM for us through their $digest cycles. When a $digest cycle is called, Angular runs through all the $scope objects we declared and checks whether their current values from the previous cycle matches the values in the next. Any objects that are found different will then be used to determine whether any DOM elements are bound to them and in which Angular will then update the DOM to reflect the new values. Things get complicated when we mix in asynchronous and synchronous functions (read: REST calls or even anything that uses a callback function) because the $scope easily gets lost along the way. And when that happens, Angular ends up creating a new isolated scope and when the next $digest cycle runs, the data is accurately updated but Angular loses the connection from the scope data to the DOM. Thus while the data stored is being accurately updated, what is displayed to the user is not.

Wtf is this?!

The Solution (The Last Watcher):

In the controller:

In the directive:

We create just one extra watcher inside our directive that binds to a scope flag. This watcher handles any and ALL edge cases whether data flows in at different rates or having several objects and user interactions that can affect the source data. Every time data is updated, our callbacks update the scope flag which is then captured by the extra watcher, which in turn carries that update all the way up to the DOM.

Clone this wiki locally