Making the most of ng-select

Angular comes prebuilt with some nice functionality, including the ng-select directive. It lets you create a select box with several options with 2-way data binding in mind. So the options in your select will be built from data in your model, and the selected value will also be captured in your model.

The basic demo on the docs page shows off a lot of what ng-select can do, but it doesn’t give examples of one of the possibilities that is defined in the “ngOptions comprehension_expression“, namely the “track by trackexpr” option.

That functionality only made it in to the unstable master branch 4 months ago (into v1.1.5), and it really makes life much easier when you’re working with an array of objects in your ng-options. You can find blog posts before this was available where people found workarounds for dealing with an array of objects (check the “Last Variation” section here), but track by trackexpr¬†really saves the day.

I put together a quick sample to show off this awesome feature. Imagine you have an array of user objects, and a stored user object in your scope:

function MyCtrl($scope) {

$scope.user = {id: 2, email: "" };
 $scope.object_array = [{ id: 1, email: "" }, { id: 2, email: "" }];

So here is how we make a select element:

<div ng-controller="MyCtrl">
 <select ng-model="user" ng-options="u as for u in object_array track by"></select>


The “u in object_array” parts will iterate over the elements of object_array and add <options> elements into our select. The text that is displayed for each option is the part. The data that is associated with each chosen option is the “u” in the “u as” part. The ng-model=”user” sets up a 2-way data binding between what is selected from the select, and $scope.user. In this case, our $scope.user starts out as one of the elements of the object_array, so we hope that the default state of the select will be one where that option is selected.

And so you can probably guess that the “track by” part is the part of the code that tells angular how to compare each option to the ng-model. That ensures that is compared to as we iterate over the object_array. And indeed, it works as you can see here:¬†

This is a pretty elegant solution that the angular folks came up with. I use the underscore library a lot, so I played around with just doing an _.isEqual comparison in the angular.js ng-options directive code base, rather than even bothering with track by. It would look something like (around line 16690 in angular.js 1.5):

if (multiple) {
selected = selectedSet.remove(trackFn ? trackFn(scope, locals) : valueFn(scope, locals)) != undefined;
 } else {
if (trackFn) {
var modelCast = {};
modelCast[valueName] = modelValue;
selected = trackFn(scope, modelCast) === trackFn(scope, locals);
} else {
selected = _.isEqual(modelValue, valueFn(scope, locals)); // my new line
// selected = modelValue === valueFn(scope, locals); // their current line
selectedSet = selectedSet || selected; // see if at least one item is selected

That seems to work if you add underscore before angular, and maybe it has some usefulness, but track by seems like a more general solution!


Leave a Reply

Fill in your details below or click an icon to log in: Logo

You are commenting using your account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s