3

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
        });  
    };
});

2 Answers 2

5

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.

Sign up to request clarification or add additional context in comments.

1 Comment

Ah, promises. I started with those but didn't come up with the deferred resolve aspect of it. I started tinkering with $rootScope but knew for sure that wasn't the answer.Thanks! Solved my problem.
2

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
        });  
    };
});

1 Comment

This did work, however, I'd rather not use a workaround to get the unwrapped promise to work in the controller. I didn't mind using the promise in the controller, but I was getting an error when a promise wasn't returned.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.