/ tutorial

<tutorial>LocalStorage with AngularJS - P.3 - Local Storage of JSON Objects</tutorial>

Welcome to the final section! We are going to cover how we can store our JSON Objects in the browser local storage for offline capabilities. I'm sure that there are going to be plenty of concerns from people about leaving data in the localstorage, and questions as to why I didn't just use cookies etc. The answer is, and probably appiles to you reading, I didn't want to use a cookie. I like to learn and repeating old practices isn't learning. In case you got here first, you can catch up with the setup topics at the links below.

  1. Installing NodeJS
  2. P.1 - MEAN less the Mongo
  3. P.2 - API Data Requests
  4. P.3 - Local Storage of JSON Objects - you are here

Let's get to it.

What we are going to want to do first is, of course, inject the LocalStorageModule into our application. If you recall, we already have the LocalStorageModule loaded in the app via the first line in our public/javascripts/app.js file.

We are going to want to configure this now. Adding a suffix will help the app determine what objects in the local storage are declared by your application. If you have a bunch of stuff then there exists the possibilty that you're going to read a similar object that may not necessarily be what you're expecting to see.
Let's add this right under our route configuration.

app.config(['localStorageServiceProvider', function(localStorageServiceProvider){  
  localStorageServiceProvider.setPrefix('myLocalStorageApp');
}]);

Next we're going to want to inject the localStorageServiceProvider into our MainCtrl. I am going to omit the controller code for berevity

...
app.controller('MainCtrl', ['$scope','$http', 'localStorageService', 
  function($scope, $http, localStorageService){
...

Before we waste any time we should check to see that your browser supports localStorage.
In your MainCtrl, perform a quick check and if it's not supported, broadcast it to your message variable.

  if(!localStorageService.isSupported){
    $scope.message = 'Your browser does not support localStorage. This app wont work offline!';
  }

Try refreshing your browser. If your message is still 'Check out this inventory' then you're in the clear.
The next part is where the data integrity specialists are going to be mad, I'm going to be mad, everyone's going to be mad. The idea is to have offline-esque capabilities of an application and we expect that there are going to be enough checks and balances on the server side for concurrency checks. The data will be a lot more important and a lot more compilcated than what we're displaying. Directly under your check for support, add the following code.

else{  
    //check to see if our inventory is stored in local storage.
    console.log('Checking local storage');
    $scope.inventory = localStorageService.get('inventory');

    if(!$scope.inventory){
      console.log('There is no inventory in your localStorage. Requesting Inventory from Srever');
      $http.get('/inventory') //get us some datas
      .success(function(data, status, headers, config){
        $scope.message = 'Check out this inventory';
        $scope.inventory = data;
        localStorageService.set('inventory', $scope.inventory);
      })
      .error(function(data, status, headers, config){
        $scope.message = 'Something exploded! '+data.message;
      });   
    }
    else{
      $scope.message = 'We found some data in your localStorage. Here it is!'
    }
  }

This is going to try to load the inventory from local storage. If it exists then we'll tell the user that we found some old work and have them continue to work on it. If it doesn't exist then we're going to make a request to the server and get some fresh data.
Pop open your page and press refresh. You're going to see the 'Check out this inventory!' message. If you hit refresh again, you will see the 'We found some data' message. This shows that our local storage is working! Balla-balla shot colla fo ralla.

Now go ahead and play round with it. make some changes. Make my kitten worth thousands of dollars.
Hit refresh..
...
No more thousand dollar kitten. It's not saving!

We can manually implement a save by pressing the done button.
OR! We can use the fancy observable pattern
Angular js supports the $scope.$watch(...) functionality. It will watch for changes to a variable and then execute some code in the form of a callback. Let's watch our inventory, shall we?

 $scope.$watch('inventory', function(){
      console.log('Inventory Changed');
      localStorageService.set('inventory', $scope.inventory);
 }, true);

Now go ahead and make my kitten worth a thousand bucks and then refresh the application. She will persist as the most valuable kitten in the world.

Now, what if we want to 'save' our data back to the server? Or if we want to request new data because we think ours is too stale? We should probably add some U.I. elements for this.
Head over to your views/partials/index.jade and let's add some buttons under the table.

      a.btn.btn-success(href='' ng-click='saveInventory()') Save
      a.btn.btn-danger(href='' ng-click = 'removeLocalStorage()') Refresh

Add some code into your public/javascripts/app.js to handle the removeLocalStorage()

$scope.removeLocalStorage = function(){
      localStorageService.remove('inventory');
      console.log('Cleared the local storage');
      $http.get('/inventory') //get us some datas
      .success(function(data, status, headers, config){
        $scope.message = 'Check out this inventory';
        $scope.inventory = data;
        localStorageService.set('inventory', $scope.inventory);
      })
      .error(function(data, status, headers, config){
        $scope.message = 'Something exploded! '+data.message;
      });   
}

Now this is functional but definitely not elegant This is the exact code for getting our inventory that we use when we load up the controller. We're going to do a small refactor now.
Take all the $http.get code and put it into a local function (not a scope function) called getInventoryFromServer()

var getInventoryFromServer = function(){
    $http.get('/inventory') //get us some datas
    .success(function(data, status, headers, config){
      $scope.message = 'Check out this inventory';
      $scope.inventory = data;
      localStorageService.set('inventory', $scope.inventory);
    })
    .error(function(data, status, headers, config){
      $scope.message = 'Something exploded! '+data.message;
    });   
  }

Now replace the locations where we made the calls with getInventoryFromServer();

...
if(!$scope.inventory){
      console.log('There is no inventory in your localStorage. Requesting Inventory from Srever');
      getInventoryFromServer();
    }
...
$scope.removeLocalStorage = function(){
      localStorageService.remove('inventory');
      console.log('Cleared the local storage');
      getInventoryFromServer();
    }
...

This is a lot easier to maintain if we ever need to adjust our requests!

It is now, my friends, that I found my fatal flaw. All my .jade files are not going to be able to render just by opening up the application. The entire point of this was to be able to have the app run as a standalone and use the Node/Express API to get and set data. That also means that we will need to enable CORS access to the API as well. All of which I will write another time.

Currently, this application now does the following:

  1. Sets up an API to get some data from.
  2. Once you load the first page, you have all the views you will need to use your application. Should you go offline, you will still be able to edit the stuff you've got. When you come back online you'll be able to update your data once it's set.
  3. Your data is stored in your local browser and is only over written if you expressly tell the application to remove it.

Later on, I'm going to revisit this application and make it straight HTML, add in CORS access, and wrap it all up with Cordova. Keep an eye on the blog if you're interested in seeing this part happen.

You can check out this code at my github.
https://github.com/Daimonos/localstorage-tutorial Commit #5

Cheers, Friends!