Delaying AngularJS route change until model loaded to prevent flicker Delaying AngularJS route change until model loaded to prevent flicker javascript javascript

Delaying AngularJS route change until model loaded to prevent flicker


$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/#/phonesSource: https://github.com/mhevery/angular-phonecat/commit/ba33d3ec2d01b70eb5d3d531619bf90153496831


Here's a minimal working example which works for Angular 1.0.2

Template:

<script type="text/ng-template" id="/editor-tpl.html">    Editor Template {{datasets}}</script><div ng-view></div>

JavaScript:

function MyCtrl($scope, datasets) {        $scope.datasets = datasets;}MyCtrl.resolve = {    datasets : function($q, $http) {        var deferred = $q.defer();        $http({method: 'GET', url: '/someUrl'})            .success(function(data) {                deferred.resolve(data)            })            .error(function(data){                //actually you'd want deffered.reject(data) here                //but to show what would happen on success..                deferred.resolve("error value");            });        return deferred.promise;    }};var myApp = angular.module('myApp', [], function($routeProvider) {    $routeProvider.when('/', {        templateUrl: '/editor-tpl.html',        controller: MyCtrl,        resolve: MyCtrl.resolve    });});​​

http://jsfiddle.net/dTJ9N/3/

Streamlined version:

Since $http() already returns a promise (aka deferred), we actually don't need to create our own. So we can simplify MyCtrl. resolve to:

MyCtrl.resolve = {    datasets : function($http) {        return $http({            method: 'GET',             url: 'http://fiddle.jshell.net/'        });    }};

The result of $http() contains data, status, headers and config objects, so we need to change the body of MyCtrl to:

$scope.datasets = datasets.data;

http://jsfiddle.net/dTJ9N/5/


I see some people asking how to do this using the angular.controller method with minification friendly dependency injection. Since I just got this working I felt obliged to come back and help. Here's my solution (adopted from the original question and Misko's answer):

angular.module('phonecat', ['phonecatFilters', 'phonecatServices', 'phonecatDirectives']).  config(['$routeProvider', function($routeProvider) {    $routeProvider.      when('/phones', {        templateUrl: 'partials/phone-list.html',         controller: PhoneListCtrl,         resolve: {             phones: ["Phone", "$q", function(Phone, $q) {                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: ["$q","$defer", function($q, $defer) {               var delay = $q.defer();               $defer(delay.resolve, 1000);               return delay.promise;              }            ]        },        }).      when('/phones/:phoneId', {        templateUrl: 'partials/phone-detail.html',         controller: PhoneDetailCtrl,         resolve: PhoneDetailCtrl.resolve}).      otherwise({redirectTo: '/phones'});}]);angular.controller("PhoneListCtrl", [ "$scope", "phones", ($scope, phones) {  $scope.phones = phones;  $scope.orderProp = 'age';}]);

Since this code is derived from the question/most popular answer it is untested, but it should send you in the right direction if you already understand how to make minification friendly angular code. The one part that my own code didn't requires was an injection of "Phone" into the resolve function for 'phones', nor did I use any 'delay' object at all.

I also recommend this youtube video http://www.youtube.com/watch?v=P6KITGRQujQ&list=UUKW92i7iQFuNILqQOUOCrFw&index=4&feature=plcp , which helped me quite a bit

Should it interest you I've decided to also paste my own code (Written in coffeescript) so you can see how I got it working.

FYI, in advance I use a generic controller that helps me do CRUD on several models:

appModule.config ['$routeProvider', ($routeProvider) ->  genericControllers = ["boards","teachers","classrooms","students"]  for controllerName in genericControllers    $routeProvider      .when "/#{controllerName}/",        action: 'confirmLogin'        controller: 'GenericController'        controllerName: controllerName        templateUrl: "/static/templates/#{controllerName}.html"        resolve:          items : ["$q", "$route", "$http", ($q, $route, $http) ->             deferred = $q.defer()             controllerName = $route.current.controllerName             $http(               method: "GET"               url: "/api/#{controllerName}/"             )             .success (response) ->               deferred.resolve(response.payload)             .error (response) ->               deferred.reject(response.message)             return deferred.promise          ]  $routeProvider    .otherwise      redirectTo: '/'      action: 'checkStatus']appModule.controller "GenericController", ["$scope", "$route", "$http", "$cookies", "items", ($scope, $route, $http, $cookies, items) ->  $scope.items = items      #etc ....    ]