Kirill Slatin
Kirill Slatin

Reputation: 6143

Two controllers depending on same data returned asynchronously

There is an app that allows users create or join rooms. So there is RoomsListController which displays rooms, RoomDetailsController which shows contents and controls for activity inside a particular room. And of cause, a RoomService which provides getRooms() (async) method.

Now what would be the Angular way to make the two controllers wait for the same data returned by RoomService.getRooms()?

Details:

Upvotes: 3

Views: 82

Answers (2)

GregL
GregL

Reputation: 38121

Another approach might be to modify the RoomService.getRooms() function in your service to declare and store a local promise the first time the function is called, while you are waiting for results from the server. If the function gets called again, you would just return the same promise immediately. Once the results are returned from the server, you can resolve the stored promise and clear it, so the next time it gets called, a new request will be made.

Of course, there is plenty of room to customise from here. If you know the data from the server will never change, for instance, you don't need to clear the promise at all. Just return it every time the rooms get asked for.

Here is a sample implementation:

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

app.controller('MainCtrl', function($scope, RoomService) {
  $scope.name = 'World';

  $scope.fetchingStatus = function() {
    return RoomService.serverRequests ? 'Fetching... Server Requests: ' + RoomService.serverRequests : '';
  };

  $scope.getRooms = function() {
    console.log('asking for rooms')
    RoomService.getRooms().then(function(rooms) {
      console.log('rooms returned: ', rooms);
      $scope.rooms = rooms;
    });
  };
});

function RoomService($q, $timeout) {
  this.$q = $q;
  this.$timeout = $timeout;
  this.getRoomsPromise = null;
  this.serverRequests = 0;
}

RoomService.prototype.getRooms = function() {
  if (!this.getRoomsPromise) {
    this.getRoomsPromise = this.$q.defer();
    this.getRoomsFromServer();
  }
  return this.getRoomsPromise.promise;
};

RoomService.prototype.getRoomsFromServer = function() {
  var rooms = [];
  for (var i = 1; i <= 10; i++) {
    rooms.push({
      id: i,
      name: 'Room ' + i
    });
  }
  console.log('fetching data from server...');
  this.serverRequests++;
  this.$timeout(function() {
    this.serverRequests--;
    this.getRoomsPromise.resolve(rooms);
    this.getRoomsPromise = null;
    console.log('got data from server...')
  }.bind(this), 1500);
}

app.service('RoomService', RoomService);
.fetching {
  background-color: red;
  color: white;
  padding: 2px 10px;
  display: inline-block;
  margin-left: 50px;
}
<script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.4.3/angular.min.js"></script>
<div ng-app="plunker" ng-controller="MainCtrl">
  <p>
    <button ng-click="getRooms()">Get Rooms</button>
    <span class="fetching" ng-show="fetchingStatus()">{{fetchingStatus()}}</span>
  </p>
  <ul>
    <li ng-repeat="room in rooms track by room.id">{{room.name}}</li>
  </ul>
</div>

Upvotes: 1

Josh Beam
Josh Beam

Reputation: 19772

Take a look at route resolve promises.

You'll call the async method before the route resolves:

// in your config
$routeProvider.when('/your-route', {
  resolve: {
    roomData: function(RoomService) {
      return RoomService.getRooms();
    }
  }
}

// RoomsListController
function(roomData) {
  // do something with roomData
}

// RoomDetailsController
function(roomData) {
  // do something with roomData
}

This way, as far as the controllers are concerned, the data is already there before the route even loads.

You've touched on one of the key issues is applications... that is, creating a single source of truth for data that is composed in a way that the application can consume it.

There are many patterns for solving this, but I'd recommend taking a look at the route resolution promises as a starting point, and branching out from there. It can become very complex.

Upvotes: 2

Related Questions