AngularJS 1.2 storing data arrays in services

Problem

I have a question, seeking advice, on how to get data that is stored in service arrays to the controller since unwrapping promises was removed in 1.2.

Example:

  1. Route one contains a list of items
  2. Route two contains a form to add a new item
  3. Once the item was saved from route tow, user is redirected back to route one.

When route one initially loaded, the service would make a request to the server for the items, store the items in an array in the service so every request for route one after that would just return an array. When an item was saved, that item was pushed to the array in the service.

If I were to wait for the promise in route one's controller on the initial load, no problem because a response was sent back, but every request to route one after that would return an error because I was returning an array.

Any ideas on how to accomplish something like this in 1.2?

app.factory('Items',function($http) {
    var items = [];
    return {
        list: function() {
            if (items.length == 0) {    // items array is empty so populate it and return list from server to controller
                return $http.get('/?items').then(function(response) {
                    items = response.data.items;
                    return response.data.items;
                });
            }
            return items;   // items exist already so just return the array
        },
        save: function(item) {
            return $http.post('/',{item:item}).then(function(response) {
                items.push(item);
                return response;
            });
        }
    }
});

app.controller('RouteOne',function($scope,Items) {
    Items.list().then(function(response) {
        $scope.items = response;
    });

    /* used to be this before unwrapped promises were removed
    $scope.items = Items.list();
    */

});

app.controller('RouteTwo',function($scope,Items) {
    $scope.new_item = {};
    $scope.addItem = function() {
        Items.save($scope.new_item).then(function(response) {
            $location.path('/');    // back to route one
        });  
    };
});
Problem courtesy of: Rob

Solution

You can have the service return its own promise, which can can resolve either with the cached value or the result of the $http promise. I've not fully tested this, but it could look something like this:

app.factory('Items', function($q, $http) {
    var items = [];
    return {
        list: function() {
            var deferred = $q.defer();
            if (items.length == 0) {    // items array is empty so populate it and return list from server to controller
                $http.get('/?items').then(function(response) {
                    items = response.data.items;
                    deferred.resolve(response.data.items);
                });
            } else {
                deferred.resolve(items);   // items exist already so just return the array
            }
            return deferred.promise;
        },
        save: function(item) {
            return $http.post('/',{item:item}).then(function(response) {
                items.push(item);
                return response;
            });
        }
    }
});

app.controller('RouteOne',function($scope,Items) {
    Items.list().then(function(response) {
        $scope.items = response;
    });

});

Also, with your particular use case you could move the deferred to the service level instead of the function level because you'll only be calling it once, but the way I've written it is a little more flexible in case you want to clear the items array.

Solution courtesy of: Geoff Genz

Discussion

The solution given below must be modified because of XHR calls

The main idea of this example is to use simple js to bind a reference to a model.

Items.save must always return the reference to its local scope array items


I made you a jsfiddle

var app = angular.module('app', ['ui.router']);

app.config(function ($stateProvider) {
    $stateProvider
        .state('one', {
            url: '/',
            template: '<ul><li data-ng-repeat="item in items">{{item.value}}</li></ul><a href=#/two>Route two</a>',
            controller: 'RouteOne'
        })
        .state('two', {
            url: '/two',
            template: '<input data-ng-model="new_item.value"></input><button data-ng-click="addItem()">Add item</button>',
            controller: 'RouteTwo'
        });
});

app.run(function ($state) {
    $state.go('one');
});

app.factory('Items',function($http) {
    var items = [];
    return {
        list: function() {
            if (items.length === 0) {    // items array is empty so populate it and return list from server to controller
                // i just modified a little the request for jsfiddle
                $http.get('/echo/jsonp/?items=[{"value": 1},{"value": 2},{"value": 3},{"value": 4},{"value": 5}]').then(function(response) {

                    // you won't need next line  with a real server
                    response.data = JSON.parse(response.data.items);
                    Array.prototype.push.apply(items, response.data);
                });

                // simply always return the reference to items
                // as it will be populated later
                return items;
            }
            return items;   // items exist already so just return the array
        },
        save: function(item) {
            return $http.post('/echo/jsonp/',{item:item}).then(function(response) {
                items.push(item);
                return response;
            });
        }
    }
});

app.controller('RouteOne',function($scope,Items) {
    // don't do anything as you the binding does the work for you
    $scope.items = Items.list();
});

app.controller('RouteTwo',function($scope,Items, $location) {
    $scope.new_item = {};
    $scope.addItem = function() {
        Items.save($scope.new_item).then(function(response) {
            $location.path('/');    // back to route one
        });  
    };
});
Discussion courtesy of: axelduch

This recipe can be found in it's original form on Stack Over Flow.