22

How do I have AngularJS show a loading spinner until the data has finished loading?

If my controller has $scope.items = [{name: "One"}] set up statically, and an AJAX loader which populates $scope.items[0]['lateLoader'] = "Hello", I'd like the spinner to show until the AJAX load is complete, and then populate the bound span with the retrieved data.

<ul ng-repeat="item in items">
  <li>
    <p>Always present: {{item.name}}</p>
    <p>Loads later: <span ng-bind="item.lateLoader"><i class="icon icon-refresh icon-spin"></i></span></p>
  </li>
</ul>

This code populates the bound span immediately, and as item.lateLoader is empty the spinner is replaced with nothing.

How should I do this cleanly?

1
  • why not to put loader image statically in the markup ? Commented Apr 23, 2013 at 16:25

4 Answers 4

23

I would create a custom directive as per the other answer, but here is how you could do it without the directive which might be a good idea to learn before getting into more complex functionality.. A couple things to note:

  1. Using a setTimeout to simulate an ajax call
  2. The loading icon is preloaded and is being hidden when the data loads. Just a simple ng-hide directive.
  3. There is no loading image in my example just some text that gets hidden (obviously your html will have the correct css references).

Demo: http://plnkr.co/edit/4XZJqnIpie0ucMNN6egy?p=preview

View:

<ul >
  <li ng-repeat="item in items">
    <p>Always present: {{item.name}}</p>
    <p>Loads later: {{item.lateLoader}} <i ng-hide="item.lateLoader"  class="icon icon-refresh icon-spin">loading image i don't have</i></p>
  </li>
</ul>

Controller:

app.controller('MainCtrl', function($scope) {
  $scope.name = 'World';
  $scope.items = [{name: "One"}];
  setTimeout(function() {
    $scope.$apply(function() {
     $scope.items[0].lateLoader = 'i just loaded';  
    });
  }, 1000);
});
Sign up to request clarification or add additional context in comments.

Comments

22

I actually have been using a solution for this for a while now that works great and is better than using a timeout in my opinion. I am using $resource, but you could do the same thing with $http. On my $resource objects, I add the bit in the following code that sets loaded to true.

$scope.customer = $resource(dataUrl + "/Course/Social/" + courseName)
    .get({}, function (data) { data.loaded = true; });

Then, in my view I can use:

ng-hide="customer.loaded"

Of course, I wouldn't use a $resource directly in a controller, I extract that to a customerRepository service, but it was more simple to show it this way here.

1 Comment

This is by far the best solution.
5

I would create custom directive and put default markup with spinner.

Here are some links on custom directives

1) Egghead videos are awesome! http://www.egghead.io/video/xoIHkM4KpHM

2) Official Angular docs on directives http://docs.angularjs.org/guide/directive

3) Good overview of angular in 60ish minutes http://weblogs.asp.net/dwahlin/archive/2013/04/12/video-tutorial-angularjs-fundamentals-in-60-ish-minutes.aspx

4 Comments

I agree with this although he could also just do a ng-show or ng-hide based of the data is there or not (some condition)
@lucuma Yeah , but it seems to be good reusable thing to live in it is own directive.
I'm pretty new to Angular - if you've an example or a link to documentation that'd be really helpful, though I'm happy to go hunting documentation myself!
@JP. I put you couple of links to start with
0

@lucuma,

Great answer, an alternative can be to inject $timeout and replace the native timeout function:

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

app.controller('MainCtrl', function($scope, $timeout) {

        $scope.name = 'World';
        $scope.items = [{name: "One"}];
        $timeout(function() {
                $scope.$apply(function() {
                        $scope.items[0].lateLoader = 'i just loaded';  
                });
        }, 1000);
});

2 Comments

The setTimeout was just being used to simulate a wait although you are right it would have been better to use $timeout.
The $timeout is useful because it wraps the callback into the $apply automatically. I.e. you can remove $scope.$apply(function() { from your example.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.