Thursday, December 11, 2014

Easy Local Storage persistence layer in angular.js

Angular.js is a MVC framework for client side javascript that gives HTML superpowers. I have only introduced a few developers to angular but so far every one of them has been blown away by how easy and incredibly useful it is for building dynamic and highly responsive web apps.

Object oriented programming in angular.js with ds.oop

One of the features I like most about angular (aside from the 2-way data binding) is that it allows you to write modular self-contained code and use dependency injection which is invaluable in large projects which would otherwise quickly become heaps of unmaintainable spaghetti code. Using dependency injection in angular, we can define a controller or service with exactly what it needs to operate clearly defined in the function parameters so that any developer looking at it later immediately knows what it touches (That is, what code it needs to run or have access to). To make use of some other oop ideas such as inheritance lets see how to implement ds.oop with angular.js. ds.oop is a lightweight object oriented framework to simplify class creation and inheritance in javascript. As you will see, it allows you to write classes in a way that feels like a class.

This short tutorial will demonstrate an easy way to persist data in HTML5 local storage using reusable code via inheritance with ds.oop in angular.js. These 2 frameworks are open-source and work quite well togeather.

Required Frameworks: To start, this is a regular angular factory that can be injected into other factories and controllers as a dependency. This is a fast function to generate a Guid using a lookup table. We will use this any time we need to create a random Identifier for something.

.factory('Guid', function () {
    var lut = []; for (var i = 0; i < 256; i++) { lut[i] = (i < 16 ? '0' : '') + (i).toString(16); }
    return function () {
        var d0 = Math.random() * 0xffffffff | 0;
        var d1 = Math.random() * 0xffffffff | 0;
        var d2 = Math.random() * 0xffffffff | 0;
        var d3 = Math.random() * 0xffffffff | 0;
        return lut[d0 & 0xff] + lut[d0 >> 8 & 0xff] + lut[d0 >> 16 & 0xff] + lut[d0 >> 24 & 0xff] + '-' +
            lut[d1 & 0xff] + lut[d1 >> 8 & 0xff] + '-' + lut[d1 >> 16 & 0x0f | 0x40] + lut[d1 >> 24 & 0xff] + '-' +
            lut[d2 & 0x3f | 0x80] + lut[d2 >> 8 & 0xff] + '-' + lut[d2 >> 16 & 0xff] + lut[d2 >> 24 & 0xff] +
            lut[d3 & 0xff] + lut[d3 >> 8 & 0xff] + lut[d3 >> 16 & 0xff] + lut[d3 >> 24 & 0xff];
    };
})

Don't worry if that looks a little intimidating. Its been heavily optimized. The important part to see here is that an angular factory is just a function that returns something. In this case we are returning a function to generate a Guid. Now we can inject this factory into controllers, directives, or even other factories. Actually, thats exacly what we are going to do. Here is the simple localStorage base class I made. This provides some simple methods to save and load the object state into localStorage. At this point you might be wondering what local storage is exactly. With local storage, web applications can store data locally within the user's browser using key/value pairs. Unlike cookies, the storage limit is far larger (at least 5MB) and information is never transferred to the server.

// Base class for persistence
.factory('LocalStorage', ['Guid', function (Guid) {
    return ds.make.class({
        init: function (id) {
            this.id = (id || Guid()).toString();
        },
        load: function () {
            var state = localStorage[this.id];
            if (state) ds.data.copy(JSON.parse(state), this, [this.id], false);
        },
        save: function () {
            localStorage[this.id] = JSON.stringify(this);
        }
    });
} ])

So as you can see this is a very simple class. Our Guid factory is injected at the top. Three methods. Init() will accept an optional id or generate one using the injected Guid factory. Save() will serialize *this and save it to local storage. and Load will deserialize the data from local storage and the load it into *this. You could use this class as it is but there is nothing interesting in this class to save so to make this useful we need to inherit it...

// FormModel inherits LocalStorage class for save/load methods
.factory('FormModel', ['LocalStorage', function (LocalStorage) {
    return ds.make.class({
        inherits: LocalStorage,
        constructor: function (id) {
            this.init(id);
        },

        reset: function () {
            this.firstName= 
            this.lastName= 
            this.age='';
        }
    });
} ])

Ok so here is another simple class but in this one we have inherited our LocalStorage class and can now make use of its methods. This class is called FormModel because it is going to hold all of the data for our particular form and to use this and tie everything together we must create a angular controller and view. Here is the super mega awesome controller

.controller('MyCtrl', ['$scope', 'FormModel', function ($scope, FormModel) {

    // create an instance of FormModel and give it the id: 'test001'
    $scope.model = new FormModel('test001'); 

} ]);

And here is the view. Angular is calling our save/load methods directly through our FormModel class and handling updates between the DOM and our model automatically via 2-way data binding. Yeah, did i mention angular.js is awesome?

    <body ng-controller="MyCtrl">
        
        <div>
            model.id:
            <input type="text" ng-model="model.id" ng-disabled="true" />
        </div>
        <div>
            First Name:   
            <input type="text" ng-model="model.firstName" />
        </div>
        <div>
            Last Name:
            <input type="text" ng-model="model.lastName" />
        </div>
        <div>
            Age (years):
            <input type="text" ng-model="model.age" />
        </div>
        
        <button ng-click="model.save()">Save</button>    
        <button ng-click="model.load()">Load</button>
        <button ng-click="model.reset()">Reset</button>
    </body>

So there it is. Now who needs a database? :)
By combining ds.oop with angular we are able to take advantage of dependency injection which simplifies code maintenance and development. We accomplish this by encapsulating our class in an angular factories. By using ds.oop we can define a base class that can be used to persist any other class to local storage by simply inheriting it. This base class could also be extended to use any other storage mechanism quite easily. Session Storage, cookies, even a server side database or cache using ajax if one so desires.

I hope you have enjoyed this short tutorial. Go forth and write code.

1 comment:

  1. Thanks for sharing these niche piece of coding to our knowledge. Here, I had a solution for my inconclusive problems & it’s really helps me a lot keep updates…
    Angular training in Chennai

    ReplyDelete