(Angular-ui-router) Show loading animation during resolve process

JavascriptAngularjsAngular Ui-Router

Javascript Problem Overview


This is a two part question:

  1. I am using the resolve property inside $stateProvider.state() to grab certain server data before loading the controller. How would I go about getting a loading animation to show during this process?

  2. I have child states that also utilise the resolve property. The problem is that ui-router seems to want to finalise all resolves before loading any controller. Is there any way I can get the parent controllers to load once their resolves have been resolved, without having to wait for all the child resolves? An answer to this will likely also solve the first problem.

Javascript Solutions


Solution 1 - Javascript

EDIT: Here is an even easier solution, tested and working nicely:

In my main controller I simply have

$scope.$on('$stateChangeStart', function(event, toState, toParams, fromState, fromParams) {
	if (toState.resolve) {
		$scope.showSpinner();
	}
});
$scope.$on('$stateChangeSuccess', function(event, toState, toParams, fromState, fromParams) {
	if (toState.resolve) {
		$scope.hideSpinner();
	}
});

This shows the spinner whenever we are about to go to a state that has anything to resolve and hides it, when the state change is complete. You might want to add some check up the state hierarchy (i.e. also show the spinner if a parent state that is being loaded resolves something) but this solution works fine for me.

Here is my old suggestion for reference and as an alternative:

  1. In your application controller, listen to the stateChangeStart event and check if you are about to switch to a state where you want to show a spinner during resolve (see https://github.com/angular-ui/ui-router/wiki/Quick-Reference#wiki-events-1)

    $rootScope.$on('$stateChangeStart', function(event, toState, toParams, fromState, fromParams){
        if (toState.name == 'state.with.resolve') {
            $scope.showSpinner();  //this is a function you created to show the loading animation
        }
    })
    
  2. When you controller finally gets called, you can hide the spinner

    .controller('StateWithResolveCtrl', function($scope) {
        $scope.hideSpinner();
    })
    

You also might want to check for any errors that may have occurred during resolve by listening to the $stateChangeError event and hiding the animation while you handle the error.

This is not totally clean as you distribute the logic for the spinner between controllers, but it's a way. Hope it helps.

Solution 2 - Javascript

I developed the following solution which works perfectly for me.

1. Add the following app.run

app.run(function($rootScope){

    $rootScope
        .$on('$stateChangeStart', 
            function(event, toState, toParams, fromState, fromParams){ 
                $("#ui-view").html("");
                $(".page-loading").removeClass("hidden");
        });

    $rootScope
        .$on('$stateChangeSuccess',
            function(event, toState, toParams, fromState, fromParams){ 
                $(".page-loading").addClass("hidden");
        });

});

2. Place the loading indicator just above the ui-view. Add id="ui-view" to ui-view div.

<div class="page-loading">Loading...</div>
<div ui-view id="ui-view"></div>

3. add the following to your css

.hidden {
  display: none !important;
  visibility: hidden !important;
}

NOTE:

A. The above code will display loading indicator in two cases 1) when the angular app is loaded first time 2) when the view is changed.

B. If you don't want the indicator to be shown when the angular app loads first time (before any view is loaded), then add hidden class to the loading div like below

<div class="page-loading hidden">Loading...</div>

Solution 3 - Javascript

I've found using the angular-loading-bar worked really well for long resolves due to network access.

Solution 4 - Javascript

How about adding content to the div that will be filled by ui-router once the properties have resolved?

In your index.html

<div ui-view class="container">
    Loading....
</div>

The user will now see "Loading..." while the properties are resolved. Once everything is ready the content will be replace by ui-router with your apps content.

Solution 5 - Javascript

I use an animated gif that I show only when $http has pending requests.

In my base page template, I have a navbar and navbar controller. The relevant part of the controller looks like this:

controllers.controller('NavbarCtrl', ['$scope', '$http',
    function ($scope, $http) {
        $scope.hasPendingRequests = function () {
            return $http.pendingRequests.length > 0;
        };
    }]);

The corresponding code in my html is:

<span class="navbar-spinner" ng-show="hasPendingRequests()">
    <img src="/static/img/spinner.gif">
</span>

I hope that helps!

Solution 6 - Javascript

My idea is to walk the path on state graph between transitioning states on $stateChangeStart and collect all involved views. Then every ui-view directive watches if corresponding view is involved in transition and adds 'ui-resolving' class on it's element.

The plunker demo introduces two root states: first and second, the latter has two substates second.sub1 and second.sub2. The state second.sub2 also targets footer view that belongs to its grandparent.

Solution 7 - Javascript

This is loader for globally when page navigate between any state (any page), put in app.js

.run(
    ['$rootScope',
        function($rootScope) {
            $rootScope.$on('$stateChangeStart', function(event, toState, toParams, fromState, fromParams) {
                $rootScope.preloader = true;
            })
            $rootScope.$on('$stateChangeSuccess', function(event, toState, toParams, fromState, fromParams) {
                $rootScope.preloader = false;
            })
        }
    ])

In html:

<div ng-show="preloader">Loading...</div>

Solution 8 - Javascript

I prefer using a directive to match any loading activity, mainly to keep my codebase clean

angular.module('$utilityElements', [])
.directive('loader',['$timeout','$rootScope', function($timeout, $rootScope) {
    return {
      restrict: 'A',
      template: '<div id="oneloader" class="hiddenload">loading...</div>',
      replace: true,
      compile: function (scope, element, attrs) {
        $timeout(function(){
          $rootScope
              .$on('$stateChangeStart',
                  function(event, toState, toParams, fromState, fromParams){
                      $("#oneloader").removeClass("hiddenload");
              });

          $rootScope
              .$on('$stateChangeSuccess',
                  function(event, toState, toParams, fromState, fromParams){
                      //add a little delay
                      $timeout(function(){
                        $("#oneloader").addClass("hiddenload");
                      },500)
              });
        }, 0);
      }
    }
  }]);

Solution 9 - Javascript

The use of $stateChangeStart and the like have since been deprecated and replaced with Transition Hooks. So, for the answer by Stefan Henze, the updated version would be:

$transitions.onStart({}, function(transition) {
  if (transition.to().resolve) {
  	$scope.showSpinner();
  }
});

$transitions.onSuccess({}, function(transition) {
  if (transition.to().resolve) {
  	$scope.hideSpinner();
  }
});

You can use this in you parent controller. Remember to inject $transitions -

.controller('parentController',['$transitions',function($transitions){...}]);

Also, keep in mind that a resolve that is an empty object will still render transition.to().resolve == true, so don't leave an empty placeholder resolve in the state declaration.

Solution 10 - Javascript

My Idea to use view while using resole in router it is working awesome. try this.

//edit index.html file 
<ion-nav-view>
    <div ng-show="loadder" class="loddingSvg">
        <div class="svgImage"></div>
    </div>
</ion-nav-view>

// css file

.loddingSvg {
    height: 100%;
    background-color: rgba(0, 0, 0, 0.1);
    position: absolute;
    z-index: 99;
    left: 0;
    right: 0;
}

.svgImage {
    background: url(../img/default.svg) no-repeat;
    position: relative;
    z-index: 99;
    height: 65px;
    width: 65px;
    background-size: 56px;
    top: 50%;
    margin: 0 auto;
}

// edit app.js

 .run(function($ionicPush, $rootScope, $ionicPlatform) {




        $rootScope.$on('$stateChangeStart',
            function(event, toState, toParams, fromState, fromParams) {
                $rootScope.loadder = true;
            });

        $rootScope.$on('$stateChangeSuccess',
            function(event, toState, toParams, fromState, fromParams) {
                $rootScope.loadder = false;
            });

});

Solution 11 - Javascript

If anyone is using ngRoute, waiting on resolve before loading the next view, and using angular-bootstrap-ui for ui, you can do the following:

app.config([
  "$routeProvider", "$locationProvider", function($routeProvider, $locationProvider) {
    return $routeProvider.when("/seasons/:seasonId", {
      templateUrl: "season-manage.html",
      controller: "SeasonManageController",
      resolve: {
        season: [
          "$route", "$q", "$http", "$modal", function($route, $q, $http, $modal) {
            var modal, promise, seasonId;
            modal = $modal.open({
              backdrop: "static",
              template: "<div>\n  <div class=\"modal-header\">\n    <h3 class=\"modal-title\">\n      Loading...\n    </h3>\n  </div>\n  <div class=\"modal-body\">\n    <progressbar\n      class=\"progress-striped active\"\n      value=\"'100'\">\n    </progressbar>\n  </div>\n</div>",
              keyboard: false,
              size: "lg"
            });
            promise = $q.defer();
            seasonId = $route.current.params.seasonId;
            $http.get("/api/match/seasons/" + seasonId).success(function(data) {
              modal.close();
              promise.resolve(data);
            }).error(function(data) {
              modal.close();
              promise.reject(data);
            });
            
            return promise.promise;
          }
        ]
      }
    });
  }
]);

Attributions

All content for this solution is sourced from the original question on Stackoverflow.

The content on this page is licensed under the Attribution-ShareAlike 4.0 International (CC BY-SA 4.0) license.

Content TypeOriginal AuthorOriginal Content on Stackoverflow
QuestionMatt WayView Question on Stackoverflow
Solution 1 - JavascriptStefan HenzeView Answer on Stackoverflow
Solution 2 - JavascriptBlesson JoseView Answer on Stackoverflow
Solution 3 - JavascriptBrian de AlwisView Answer on Stackoverflow
Solution 4 - JavascriptWolfgangView Answer on Stackoverflow
Solution 5 - JavascriptjoshvillbrandtView Answer on Stackoverflow
Solution 6 - JavascriptaikovenView Answer on Stackoverflow
Solution 7 - JavascriptmanozView Answer on Stackoverflow
Solution 8 - JavascriptvorillazView Answer on Stackoverflow
Solution 9 - JavascriptIB2DEVView Answer on Stackoverflow
Solution 10 - Javascriptkoneri ranjith kumarView Answer on Stackoverflow
Solution 11 - JavascriptBlaskoviczView Answer on Stackoverflow