Thursday, 5 January 2017

Angular promises

I have been struggling to understand Angular promises for months, and I finally found an answer on Stack Overflow that explains them really well. There's also this really helpful cartoon which has a nice story explaining the fetching of data. However, the Stack Overflow answer was what really did it for me, and fully deserves its current status of 383 upvotes.

The problem

The problem that promises set out to solve is that when you make asynchronous calls to a server where the results of one call are dependent on another call, you need to return the results in the order of your dependencies, not in the random order that will happen depending on which server call returns its results first.



Example

Say I have a page where users can subscribe to or unsubscribe from email mailing lists and RSS feeds. The data displayed on the page is an amalgam of two different services fetching data from two different servers.

The unsubscribe action and the subscribe action both send calls to the server. As we don't know which call will return a result first, we need to run them asynchronously in the background.

This means there are six different asynchronous calls to the server (services in Angular):
  • fetch list of mailing lists (HTTP GET request)
  • fetch list of RSS feeds (HTTP GET request)
  • subscribe to a mailing list (HTTP POST request)
  • subscribe to an RSS feed (HTTP POST request)
  • unsubscribe from a mailing list (HTTP POST request)
  • unsubscribe from an RSS feed (HTTP POST request)
The two lists of RSS feeds and mailing lists (with a status indicator to show whether the user is subscribed to them or not) will be refreshed when the results are returned.

Clicking on the toggle button will send a request to the server. As the request is asynchronous, I can then click another button and send another request. The second click might happen before the first click has returned a result. I want the page to refresh each time and display my up-to-date list of subscriptions.

In order to do this, I need to use an Angular promise.

First I have function calls within my controller to each of my data fetching and sending services.
 $scope.loadEmailLists = function () {
      $scope.emailLists = [];
             
      var myEmailLists = getEmailLists.fetch(function (emailLists) {
      $scope.emailLists = emailLists;
      $scope.loading = false;
      };
};


 $scope.loadRssFeeds = function () {
      $scope.rssFeeds = [];
             
      var myRssFeeds = getRssFeeds.fetch(function (rssFeeds) {
      $scope.rssFeeds = rsFeeds;
      $scope.loading = false;
      };
};
Then I use a deferred promise to display the results of each of my function calls.
var deferred = $q.defer();var promise = deferred.promise.then(loadEmailLists).then(loadRssFeeds);
Don't forget to inject the $q service into your controller.