Delaying AngularJS route change until model loaded to prevent flicker

Misko Hevery picture Misko Hevery · Aug 15, 2012 · Viewed 155.7k times · Source

I am wondering if there is a way (similar to Gmail) for AngularJS to delay showing a new route until after each model and its data has been fetched using its respective services.

For example, if there were a ProjectsController that listed all Projects and project_index.html which was the template that showed these Projects, Project.query() would be fetched completely before showing the new page.

Until then, the old page would still continue to show (for example, if I were browsing another page and then decided to see this Project index).

Answer

Misko Hevery picture Misko Hevery · Aug 15, 2012

$routeProvider resolve property allows delaying of route change until data is loaded.

First define a route with resolve attribute like this.

angular.module('phonecat', ['phonecatFilters', 'phonecatServices', 'phonecatDirectives']).
  config(['$routeProvider', function($routeProvider) {
    $routeProvider.
      when('/phones', {
        templateUrl: 'partials/phone-list.html', 
        controller: PhoneListCtrl, 
        resolve: PhoneListCtrl.resolve}).
      when('/phones/:phoneId', {
        templateUrl: 'partials/phone-detail.html', 
        controller: PhoneDetailCtrl, 
        resolve: PhoneDetailCtrl.resolve}).
      otherwise({redirectTo: '/phones'});
}]);

notice that the resolve property is defined on route.

function PhoneListCtrl($scope, phones) {
  $scope.phones = phones;
  $scope.orderProp = 'age';
}

PhoneListCtrl.resolve = {
  phones: function(Phone, $q) {
    // see: https://groups.google.com/forum/?fromgroups=#!topic/angular/DGf7yyD4Oc4
    var deferred = $q.defer();
    Phone.query(function(successData) {
            deferred.resolve(successData); 
    }, function(errorData) {
            deferred.reject(); // you could optionally pass error data here
    });
    return deferred.promise;
  },
  delay: function($q, $defer) {
    var delay = $q.defer();
    $defer(delay.resolve, 1000);
    return delay.promise;
  }
}

Notice that the controller definition contains a resolve object which declares things which should be available to the controller constructor. Here the phones is injected into the controller and it is defined in the resolve property.

The resolve.phones function is responsible for returning a promise. All of the promises are collected and the route change is delayed until after all of the promises are resolved.

Working demo: http://mhevery.github.com/angular-phonecat/app/#/phones Source: https://github.com/mhevery/angular-phonecat/commit/ba33d3ec2d01b70eb5d3d531619bf90153496831