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.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.