On the Business Engineering team at New Relic, we rely on AngularJS to build and maintain a variety of internal apps to help us keep shipping new features and products. We build rich user interfaces that leverage the browser to support the daily internal operations. AngularJS provides an excellent base to let them grow.

As soon as we started our second AngularJS application, however, we realized that we were having to make the same decisions all over again: From training the new hires on GUI programming and Angular's intricacies to then building a framework on top of Angular, and finally building the application. We needed a way to simplify and rationalize the process.

Web GUI vs. MVC

The first thing that hits you when diving into Web GUI development is that, despite the naming conventions, the patterns to build the application are slightly different compared to model-view-controller (MVC) server-side frameworks. MVCs rely heavily on the browser to translate what the user wants to do into the business logic that the application holds. Views are composed in the server-side, and objects have a shorter life-span: the request cycle.

Richer, more complex client GUIs need a different toolset to solve a handful of problems, which traditionally have been addressed by native GUI development:

  • Keeping the application state throughout the user interaction
  • Installing the application in the user's device
  • Building a model that reacts to the user's decisions and perception.

(Yehuda Katz condenses this dilemma very well in a 30-minute talk, A Tale of Two MVCs.)

To address these issues, we began establishing our own conventions to shape current and forthcoming applications in a uniform manner. AngularJS offers a fantastic foundation to build the framework to construct client-side applications. In fact, we built a sample application—¡Hola, Playlists!—to synthesize all our experiences and opinions in AngularJS in a workshop for New Relic employees.

Let’s take a look at the first of the conventions we found useful to build the foundation that supports our GUIs, which addresses putting the View-Controller into shape using Computed Properties. (In subsequent posts, we’ll address additional conventions we’ve established, including ways to let controllers communicate to each other as well as how to mediate routing and nested views to hold together various parts of your application.)

Computed Properties have been extensively used to represent the state and its dependencies in an application’s UI, propagating changes throughout the data-binding pipeline. In Java you can observe a Bound Property through a PropertyChangeListener. Cocoa and Objective-C provide Key-Value Observing to accomplish the same mission. Apple’s new Swift programming language incorporates a special syntax called Computed Properties. In the Javascript browser-based space, Ember.js includes Computed Properties as part of the core Object Model and the future ECMAScript already enables data-binding through the Object.observe method.

hola-playlists-main-660

The View-Controller: Computed Properties

Angular's Controllers hold the representation of the knowledge—the traditional model— and also act as the link between the user and the system. While the UI is automatically updated by using bidirectional binding to the $scope, Angular doesn't provide a convention to represent richer models, other than assigning POJOs and functions to it: Basically, static properties.

Without an existing convention, we found ourselves trying to name the new patterns we were using without using lower level concepts such as listeners, objects, or scope. And that's how we found out about Computed Properties: a little tool to take a source property and create a new value, syncing any change to it whenever the source changes.

Computed Properties are made out of three components:

  1. A variable to store the results of the computation. This is usually assigned to the $scope to make it available to the template and child controllers
  2. A Property Observer implementing the value transformation needed
  3. A watcher that triggers the observer every time an attribute in the collection changes.

Transforming properties and creating new values

For our sample application, we wanted to create playlists out of the selected tunes, enabling the “Create Playlist” button only when at least one song has been selected. We used a Computed Property to represent the state of the button—enabled or disabled—and aggregate the selected tracks.

hola-playlists-create-playlist-660

First, we need to aggregate all the selected tracks every time the user selects one by checking the associated checkbox:

<table>



<tbody>



<tr ng-repeat="track in tracks">



<td><input type="checkbox" ng-model="track.selected"/></td>



<td>{{ track.name }}</td>



<td>{{ track.artists | pluck:'name' | join:', ' }}</td>



<td>{{ track.album.name }}</td>



</tr>



</tbody>



</table>

The controller associated to this view, by either using the ng-controller directive or binding it to a template in a route, can easily watch the collection of tracks and filter out those that aren't selected. This controller becomes the root controller of this view.



// tracks is an Array injected from the resolve section in the current route.



app.controller('TracksController', ['$scope', 'tracks', function($scope, tracks){



$scope.tracks = tracks;



 



$scope.selectedTracks;



this.selectedTracksWillChange = function(tracks) {



$scope.selectedTracks = tracks.filter(function(track){



return track.selected;



});



};



$scope.$watch('tracks', this.selectedTracksWillChange, true);



}]);

We can finally add our Create Playlist button, governed by a controller in charge of watching how many tunes did the user select, enabling or disabling the button in consequence.

<button ng-controller="PlaylistController"



ng-click="createPlaylist()"



ng-disabled="createPlaylistDisabled">



Create a Playlist



</button>

We can also use a Computed Property to persist the state of the button, publishing it to the view through the $scope object. Each time the selectedTracks property changes in the root controller, TracksController, createPlaylistDisabled is computed and made available to the template, triggering Angular's render loop.

app.controller('PlaylistController', ['$scope', function($scope){



$scope.createPlaylistDisabled = true;



this.createPlaylistDisabledWillChange = function(selectedTracks){



$scope.createPlaylistDisabled = (selectedTracks.length == 0);



};



$scope.$watch('selectedTracks', this.createPlaylistDisabledWillChange, true)



}]);

We found that Computed Properties helped us to build useful, ubiquitous conventions for use by developers in New Relic’s Business Engineering team. They also helped us write our code for fellow human beings, revealing what the code is supposed to do and providing a description of how the model gets transformed when the user interacts with the interface.

Of course, we’re still battle-testing common ways to shape applications against many different scenarios to find the most robust architecture. So this is just the first in a series of posts presenting the lessons we’ve learned about JavaScript GUI development. Planned follow-up posts will cover patterns of controller communication and how routes mediate to stitch your application together.