Managing Client-Only State in AngularJS

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:

Continue reading this article on SitePoint

There is a typo in one of the code samples. The translate filter should read:

.filter('translate', function() {
	return function(text, translations) {
		return translations[text] || translations['default'] || '';
	};
});

This topic was automatically closed 91 days after the last reply. New replies are no longer allowed.