Angularjs service to manage dataset across multiple controllers

Problem

Basically the core of my app centers around a set of data retrieved from the server via a $http request. Once the data is available to the client (as an array of objects) I require it for multiple views and would like to maintain it's state between them, for example, if it has been filtered I would like only the filtered data to be available in the other views.

Currently I have a basic service retrieving the data and am then managing the state of the data (array) in an app-wide controller (see below). This works Ok but it is beginning to become a mess as I try to maintain the array length, filtered status, visible / hidden objects across controllers for each view as I have to keep a track of currentVenue etc in the app-wide controller. Note: I am using ng-repeat in each view to show and filter the data (another reason I would like to just have it filtered in a central spot).

Obviously this is not optimal. I assume I should be using a service to maintain the array of venue objects, so it would contain the current venue, current page, be responsible for filtering the array etc. and just inject it into each controller. My question is, how can I set up a service to have this functionality (including loading the data from the server on start; this would be a good start tbh) such that I can achieve this an then bind the results to the scope. ie: something $scope.venues = venues.getVenues and $scope.current = venues.currentVenue in each views controller.

services.factory('venues', function ($http, $q) {
    var getVenues = function() {
        var delay = $q.defer();

        $http.get('/api/venues', {
            cache: true
        }).success(function (venues) {
            delay.resolve(venues);
        });

        return delay.promise;
    }

    return {
        getVenues: getVenues
    }
});

controllers.controller('AppCtrl', function (venues, $scope) {
    $scope.venuesPerPage = 3;

    venues.getVenues().then(function (venues) {
        $scope.venues = venues;
        $scope.numVenues = $scope.venues.length;
        $scope.currentPage = 0;
        $scope.currentVenue = 0;
        $scope.numPages = Math.ceil($scope.numVenues / $scope.venuesPerPage) - 1;
    }
});

Sorry for the long wording, not sure how to specify it exactly. Thanks in advance.

Problem courtesy of: adamK

Solution

I threw this together quickly as a starting point. You can restructure factory any way you want. The general idea is all data in scope has now been moved to an object in factory service.

Instead of resolving the $http with just the response array, resolve it with a much bigger object that includes the array from server. Since all data is now in an object it can be updated from any controller

services.factory('venues', function ($http, $q) {
    var getVenues = function(callback) {
        var delay = $q.defer();

        $http.get('/api/venues', {
            cache: true
        }).then(function (response) {
                /* update data object*/
                venueData.venues=response.data;
                venueData.processVenueData();
                /* resolve with data object*/
                delay.resolve(venueData);
         }).then(callback);

        return delay.promise;
    }

    var processVenueData=function(){
        /* do some data manipulation here*/
        venueData.updateNumPages();
    }


    var venueData={
        venuesPerPage : 3,
        numVenues:null,
        currentVenue:0,
        numPages:null,
        venues:[],
        updateNumPages:function(){
            venueData.numPages = Math.ceil(venueData.numVenues / venueData.venuesPerPage) - 1;
        },
         /* create some common methods used by all controllers*/
        addVenue: function( newVenue){
             venueData.venues.push( newVenue)
        }
    }

    return {
        getVenues: getVenues
    }
});

controllers.controller('AppCtrl', function (venues, $scope) {  

    venues.getVenues(function (venueData) {
      /* now have much bigger object instead of multiple variables in each controller*/
       $scope.venueData=venueData;
    })
});

Now in markup reference venueData.venues or venueData.numPages

By sharing methods across controllers you can now simply bind a form object with ng-model's to a button that has ng-click="venueData.addVenue( formModel)" (or use ng-submit) and you can add a new venue from any controller/directive without adding a bit of code to the controller

Solution courtesy of: charlietfl

Discussion

The tactic is to take advantage of object references. If you move your shared data to an object, then set that object to $scope, any change on $scope is directly changing the service object since they are the same thing ($scope is referencing the service).

Here's a live sample demonstrating this technique (click).

  <div ng-controller="controller-one">
    <h3>Controller One</h3>
    <input type="text" ng-model="serv.foo">
    <input type="text" ng-model="serv.bar">
  </div>
    <div ng-controller="controller-two">
    <h3>Controller Two</h3>
    <input type="text" ng-model="serv.foo">
    <input type="text" ng-model="serv.bar">
  </div>

js:

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

app.factory('myService', function() {
  var myService = {
    foo: 'abc',
    bar: '123'
  };
  return myService;
});

app.controller('controller-one', function($scope, myService) {
  $scope.serv = myService;
});

app.controller('controller-two', function($scope, myService) {
  $scope.serv = myService;
});
Discussion courtesy of: m59

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