Learning Angular has been one of the greatest productivity boosts for rapid application development in my career. However, some of the common strategies implemented can be improved in my opinion.

In AngularJS, there is a great deal of importance placed on separation of concerns. One of the most practiced patterns for holding application state is move this data into angular services or factories. Which one of the former to use is totally a personal preference in my opinion (I opt for factories most of the time).

The purpose of this post is aimed at services that hold collections that update from remote data calls.

Targeted AngularJS version at time of writing: 1.2.19

Too many times, I have run across angular controllers bringing in the $scope service just to $watch a service collection:

The bad:

app.controller('Ctrl1', function($scope, DataFactory) { // bind the controller property to the service collection this.items = DataFactory.items; // watch the collection for changes $scope.$watch(watchSource, function(current, previous){ this.items = current; }); function watchSource(){ return DataFactory.items; } }); app.factory('DataFactory', function($q, $timeout) { var svc = {}; svc.items = []; svc.getDataStream = function() { var fakeData = [ { id: 1, name: 'name 1' }, { id: 2, name: 'name 2' }, { id: 4, name: 'name 4' } ]; // using $q to fake async data grab return $q.when(fakeData) .then(function(data) { svc.items = data; }); }; return svc; });

I know what you are thinking “why doesn’t this just work by default?” We are clearly updating the DataFactory.items .

svc.getDataStream = function() { return $q.when(fakeData) .then(function(data) { // here we are clearly reseting the data // to the response of the call.. Why doesn't // it just databind? svc.items = data; }); };

The reason that angular does not “watch” this value is because when it set’s up the implicit $watch from a view, it references the original array from the DataFactory . But when the data comes back through $http , it replaces the property with a different array reference. Thus Angular can’t watch the collection without adding nasty $watch functions in your controllers.

So why is the $watch a bad thing in the controller?

It adds to the cognitive load needed to understand what is going on in the controller. When we look at code that others (or ourselves 6 months from now) wrote, being able to easily understand the what without spending a lot of time parsing the how is very beneficial. Any bindings in your view cause implicit watches to be set. Also, all $watch functions are executed for every $digest which may occur many times in a “digest cycle” (angular kicks these off with most interactions). Adding yet another watch is a pattern that may get you into performance issues in the future.

Surely we can remove the dependency on $scope just to watch this collection. Let us look at a very simple example of this separation adapted from Todd Motto.

The better:

app.controller('Ctrl1', function(DataFactory) { // bind the controller property to the service collection this.items = DataFactory.items; // invoke the call to get data DataFactory .getDataStream() .then(function() { // update the controller collection property this.items = DataFactory.items; }.bind(this)); }); // sample "service" for getting data app.factory('DataFactory', function($q, $timeout) { var svc = {}; svc.items = []; svc.getDataStream = function() { var fakeData = [ { id: 1, name: 'name 1' }, { id: 2, name: 'name 2' }, { id: 4, name: 'name 4' } ]; // using $q to fake async data grab return $q.when(fakeData) .then(function(data) { svc.items = data; }); }; return svc; });

Now I’m not hating on this format. Here after every call to update the service data source, both the source and controller reference get updated. It works for what it is intended for and it is fairly easy to understand what is going on. However, it does not really address controllers other than the one calling the service updated being notified.

How about this:

app.controller('Ctrl1', function(DataFactory) { // bind the controller property to the service collection this.items = DataFactory.items; // but wouldn't it be so much better // to just call it and let it work DataFactory.getDataStream(); });

Doesn’t this just read so much cleaner?

Enter angular.copy . What angular.copy does when given a new array and a source array is empty the source (by setting length to 0 i think..) and then repopulate the array with the new array items.

Our end result:

var app = angular.module('app', []); app.controller('Ctrl1', function(DataFactory) { this.items = DataFactory.items; DataFactory.getDataStream(); }); app.controller('Ctrl2', function($timeout, DataFactory) { // when this eventually fires and gets *remote* data again // our other controller will automatically sync up // without the need for the $watch function $timeout(DataFactory.getDataStream, 2000); }); app.factory('DataFactory', function($q) { var svc = {}; svc.items = []; svc.getDataStream = function() { var fakeData = [ { id: 1, name: 'name 1' }, { id: 2, name: 'name 2' }, { id: 4, name: 'name 4' } ]; // using $q to fake async data grab return $q.when(fakeData) .then(function(data) { // this is the magic angular.copy(data, svc.items); }); }; return svc; });

This article was originally posted on my blog.