Originally published at: http://www.sitepoint.com/managing-client-state-angularjs/
View models in JavaScript frameworks such as AngularJS can be different from domain models on the server – a view model doesn’t even have to exist on the server. It follows then that view models can have client only state, e.g. ‘animation-started’ and ‘animation-ended’ or ‘dragged’ and ‘dropped’. This post is going to concentrate on state changes when creating and saving view models using Angular’s $resource
service.
It’s actually very easy for a $resource
consumer, e.g. a controller, to set state, as shown below.
angular.module('clientOnlyState.controllers') .controller('ArticleCtrl', function($scope, $resource, ArticleStates /* simple lookup */) { var Article = $resource('/article/:articleId', { articleId: '@id' });var article = new Article({ id: 1, title: 'A title', author: 'M Godfrey' }); article.state = ArticleStates.NONE; // "NONE" $scope.article = article; $scope.save = function() { article.state = ArticleStates.SAVING; // "SAVING" article.$save(function success() { article.state = ArticleStates.SAVED; // "SAVED" }); }; });
This approach is fine for applications containing single consumers. Imagine how boring and error prone replicating this code would be for multiple consumers! But, what if we could encapsulate the state change logic in one place?
$resource
Services
Let’s start by pulling out our Article
resource into an injectable service. Let’s also add the most trivial setting of state to NONE when an Article
is first created.
angular.module('clientOnlyState.services') .factory('Article', function($resource, ArticleStates) {var Article = $resource('/article/:articleId', { articleId: '@id' }); // Consumers will think they're getting an Article instance, and eventually they are... return function(data) { var article = new Article(data); article.state = ArticleStates.NONE; return article; } });
What about retrieving and saving? We want Article
to appear to consumers as a $resource
service, so it must consistently work like one. A technique I learned in John Resig’s excellent book “Secrets of the JavaScript Ninja” is very useful here – function wrapping. Here is his implementation directly lifted into an injectable Angular service.
angular.module('clientOnlyState.services') .factory('wrapMethod', function() { return function(object, method, wrapper) { var fn = object[method];return object[method] = function() { return wrapper.apply(this, [fn.bind(this)].concat( Array.prototype.slice.call(arguments)) ); }; } });
This allows us to wrap the save
and get
methods of Article
and do something different/additional before and after: