Écran de chargement angularjs sur demande ajax


117

En utilisant Angularjs, je dois afficher un écran de chargement (un simple spinner) jusqu'à ce que la requête ajax soit terminée. Veuillez suggérer une idée avec un extrait de code.




La meilleure pratique est d'utiliser $httpProvider.interceptorscar il peut gérer le début et la fin de la requête ajax. C'est pourquoi il $watchn'est plus nécessaire de détecter le début et la fin de l'appel ajax.
Frank Myat jeu.12

Que diriez-vous de vérifier mon usine angular-httpshooter , cela vous donne un meilleur contrôle pour les chargeurs et le gel de l'interface utilisateur
Siddharth

Réponses:


210

Au lieu de configurer une variable de portée pour indiquer l'état de chargement des données, il est préférable qu'une directive fasse tout pour vous:

angular.module('directive.loading', [])

    .directive('loading',   ['$http' ,function ($http)
    {
        return {
            restrict: 'A',
            link: function (scope, elm, attrs)
            {
                scope.isLoading = function () {
                    return $http.pendingRequests.length > 0;
                };

                scope.$watch(scope.isLoading, function (v)
                {
                    if(v){
                        elm.show();
                    }else{
                        elm.hide();
                    }
                });
            }
        };

    }]);

Avec cette directive, tout ce que vous avez à faire est de donner à tout élément d'animation de chargement un attribut 'loading':

<div class="loading-spiner-holder" data-loading ><div class="loading-spiner"><img src="..." /></div></div>

Vous pouvez avoir plusieurs fileurs de chargement sur la page. où et comment mettre en page ces spinners dépend de vous et la directive l'activera / le désactivera automatiquement automatiquement.


ici la directive "loading" est appelée deux fois, avec $ location.path (). D'abord avec la page actuelle et ensuite avec la page de destination. Quelle devrait être la raison?
Sanjay D

Excellent. Et aussi, vous pouvez utiliser jquery toggle sur watch: elm.toggle (v). J'ai un moniteur de session $ timeout, et pour moi, je n'ai besoin d'afficher le spinner qu'après une demi-seconde. Je vais implémenter un css avec transition pour montrer plus lentement le spinner.
Joao Polo

7
Si vous obtenez l'erreur "elm.show () n'est pas une fonction", vous devez ajouter jquery avant de charger angular.
morpheus05

La façon dont vous faites scope.watch ne fonctionne pas pour moi. Je devais le faire comme jzm le fait (en utilisant '' autour du nom et en omettant le préfixe de portée). Des idées pourquoi?
KingOfHypocrites

3
Et si je dois asynchroniser. charger plusieurs zones différentes?
Vadim Ferderer

56

Voici un exemple. Il utilise la méthode simple ng-show avec un booléen.

HTML

<div ng-show="loading" class="loading"><img src="...">LOADING...</div>
<div ng-repeat="car in cars">
  <li>{{car.name}}</li>
</div>
<button ng-click="clickMe()" class="btn btn-primary">CLICK ME</button>

ANGULARJS

  $scope.clickMe = function() {
    $scope.loading = true;
    $http.get('test.json')
      .success(function(data) {
        $scope.cars = data[0].cars;
        $scope.loading = false;
    });
  }

Bien sûr, vous pouvez déplacer le code html de la boîte de chargement dans une directive, puis utiliser $ watch sur $ scope.loading. Dans quel cas:

HTML :

<loading></loading>

DIRECTIVE ANGULARJS :

  .directive('loading', function () {
      return {
        restrict: 'E',
        replace:true,
        template: '<div class="loading"><img src="..."/>LOADING...</div>',
        link: function (scope, element, attr) {
              scope.$watch('loading', function (val) {
                  if (val)
                      $(element).show();
                  else
                      $(element).hide();
              });
        }
      }
  })

PLUNK : http://plnkr.co/edit/AI1z21?p=preview


La façon dont vous faites scope.watch fonctionne pour moi, contrairement à l'approche de réponse acceptée.
KingOfHypocrites

@jzm Une si bonne solution 😊, malheureusement cette solution ne fonctionne pas avec ui-router, je ne sais pas pourquoi
Lu

J'ai fait cela dans l'un de mes projets a bien fonctionné, dans un autre projet, je pense qu'en raison d'une forte demande d'ajax loading=true, ne vous mettez pas en place
alamnaryab

21

J'utilise ngProgress pour cela.

Ajoutez 'ngProgress' à vos dépendances une fois que vous avez inclus les fichiers script / css dans votre HTML. Une fois que vous faites cela, vous pouvez configurer quelque chose comme ça, qui se déclenchera lorsqu'un changement d'itinéraire a été détecté.

angular.module('app').run(function($rootScope, ngProgress) {
  $rootScope.$on('$routeChangeStart', function(ev,data) {
    ngProgress.start();
  });
  $rootScope.$on('$routeChangeSuccess', function(ev,data) {
    ngProgress.complete();
  });
});

Pour les demandes AJAX, vous pouvez faire quelque chose comme ceci:

$scope.getLatest = function () {
    ngProgress.start();

    $http.get('/latest-goodies')
         .success(function(data,status) {
             $scope.latest = data;
             ngProgress.complete();
         })
         .error(function(data,status) {
             ngProgress.complete();
         });
};

N'oubliez pas d'ajouter «ngProgress» aux dépendances des contrôleurs avant de le faire. Et si vous effectuez plusieurs requêtes AJAX, utilisez une variable incrémentielle dans la portée principale de l'application pour suivre la fin de vos requêtes AJAX avant d'appeler 'ngProgress.complete ();'.


15

l'utilisation de pendingRequests n'est pas correcte car, comme mentionné dans la documentation Angular, cette propriété est principalement destinée à être utilisée à des fins de débogage.

Ce que je recommande, c'est d'utiliser un intercepteur pour savoir s'il y a un appel Async actif.

module.config(['$httpProvider', function ($httpProvider) {
    $httpProvider.interceptors.push(function ($q, $rootScope) {
        if ($rootScope.activeCalls == undefined) {
            $rootScope.activeCalls = 0;
        }

        return {
            request: function (config) {
                $rootScope.activeCalls += 1;
                return config;
            },
            requestError: function (rejection) {
                $rootScope.activeCalls -= 1;
                return rejection;
            },
            response: function (response) {
                $rootScope.activeCalls -= 1;
                return response;
            },
            responseError: function (rejection) {
                $rootScope.activeCalls -= 1;
                return rejection;
            }
        };
    });
}]);

puis vérifiez si activeCalls est égal à zéro ou non dans la directive via un $ watch.

module.directive('loadingSpinner', function ($http) {
    return {
        restrict: 'A',
        replace: true,
        template: '<div class="loader unixloader" data-initialize="loader" data-delay="500"></div>',
        link: function (scope, element, attrs) {

            scope.$watch('activeCalls', function (newVal, oldVal) {
                if (newVal == 0) {
                    $(element).hide();
                }
                else {
                    $(element).show();
                }
            });
        }
    };
});

7

La meilleure façon de faire est d'utiliser des intercepteurs de réponse avec une directive personnalisée . Et le processus peut encore être amélioré en utilisant le mécanisme pub / sub en utilisant les méthodes $ rootScope. $ Broadcast et $ rootScope. $ On .

Comme l'ensemble du processus est documenté dans un article de blog bien écrit , je ne vais pas le répéter ici encore. Veuillez vous référer à cet article pour trouver votre implémentation nécessaire.


4

En référence à cette réponse

https://stackoverflow.com/a/17144634/4146239

Pour moi, c'est la meilleure solution, mais il existe un moyen d'éviter d'utiliser jQuery.

.directive('loading', function () {
      return {
        restrict: 'E',
        replace:true,
        template: '<div class="loading"><img src="http://www.nasa.gov/multimedia/videogallery/ajax-loader.gif" width="20" height="20" />LOADING...</div>',
        link: function (scope, element, attr) {
              scope.$watch('loading', function (val) {
                  if (val)
                      scope.loadingStatus = 'true';
                  else
                      scope.loadingStatus = 'false';
              });
        }
      }
  })

  .controller('myController', function($scope, $http) {
      $scope.cars = [];
      
      $scope.clickMe = function() {
        scope.loadingStatus = 'true'
        $http.get('test.json')
          .success(function(data) {
            $scope.cars = data[0].cars;
            $scope.loadingStatus = 'false';
        });
      }
      
  });
<body ng-app="myApp" ng-controller="myController" ng-init="loadingStatus='true'">
        <loading ng-show="loadingStatus" ></loading>
  
        <div ng-repeat="car in cars">
          <li>{{car.name}}</li>
        </div>
        <button ng-click="clickMe()" class="btn btn-primary">CLICK ME</button>
  
</body>

Vous devez remplacer $ (element) .show (); et (élément) .show (); avec $ scope.loadingStatus = 'true'; et $ scope.loadingStatus = 'false';

Ensuite, vous devez utiliser cette variable pour définir l'attribut ng-show de l'élément.


4

Implémentation typographique et angulaire

directif

((): void=> {
    "use strict";
    angular.module("app").directive("busyindicator", busyIndicator);
    function busyIndicator($http:ng.IHttpService): ng.IDirective {
        var directive = <ng.IDirective>{
            restrict: "A",
            link(scope: Scope.IBusyIndicatorScope) {
                scope.anyRequestInProgress = () => ($http.pendingRequests.length > 0);
                scope.$watch(scope.anyRequestInProgress, x => {            
                    if (x) {
                        scope.canShow = true;
                    } else {
                        scope.canShow = false;
                    }
                });
            }
        };
        return directive;
    }
})();

Portée

   module App.Scope {
        export interface IBusyIndicatorScope extends angular.IScope {
            anyRequestInProgress: any;
            canShow: boolean;
        }
    }  

Modèle

<div id="activityspinner" ng-show="canShow" class="show" data-busyindicator>
</div>

CSS
#activityspinner
{
    display : none;
}
#activityspinner.show {
    display : block;
    position : fixed;
    z-index: 100;
    background-image : url('data:image/gif;base64,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') 
    -ms-opacity : 0.4;
    opacity : 0.4;
    background-repeat : no-repeat;
    background-position : center;
    left : 0;
    bottom : 0;
    right : 0;
    top : 0;
}

3

Si vous utilisez Restangular (ce qui est génial), vous pouvez créer une animation pendant les appels API. Voici ma solution. Ajoutez un intercepteur de réponse et un intercepteur de demande qui envoie une diffusion rootscope. Créez ensuite une directive pour écouter cette réponse et cette demande:

         angular.module('mean.system')
  .factory('myRestangular',['Restangular','$rootScope', function(Restangular,$rootScope) {
    return Restangular.withConfig(function(RestangularConfigurer) {
      RestangularConfigurer.setBaseUrl('http://localhost:3000/api');
      RestangularConfigurer.addResponseInterceptor(function(data, operation, what, url, response, deferred) {
        var extractedData;
        // .. to look for getList operations
        if (operation === 'getList') {
          // .. and handle the data and meta data
          extractedData = data.data;
          extractedData.meta = data.meta;
        } else {
          extractedData = data.data;
        }
        $rootScope.$broadcast('apiResponse');
        return extractedData;
      });
      RestangularConfigurer.setRequestInterceptor(function (elem, operation) {
        if (operation === 'remove') {
          return null;
        }
        return (elem && angular.isObject(elem.data)) ? elem : {data: elem};
      });
      RestangularConfigurer.setRestangularFields({
        id: '_id'
      });
      RestangularConfigurer.addRequestInterceptor(function(element, operation, what, url) {
        $rootScope.$broadcast('apiRequest');
        return element;
      });
    });
  }]);

Voici la directive:

        angular.module('mean.system')
  .directive('smartLoadingIndicator', function($rootScope) {
    return {
      restrict: 'AE',
      template: '<div ng-show="isAPICalling"><p><i class="fa fa-gear fa-4x fa-spin"></i>&nbsp;Loading</p></div>',
      replace: true,
      link: function(scope, elem, attrs) {
        scope.isAPICalling = false;

        $rootScope.$on('apiRequest', function() {
          scope.isAPICalling = true;
        });
        $rootScope.$on('apiResponse', function() {
          scope.isAPICalling = false;
        });
      }
    };
  })
;

Toute réponse API tuera l'animation. Vous devez stocker des informations supplémentaires sur la demande-réponse et ne réagir que pour cette réponse initialisée par la demande.
Krzysztof Safjanowski

3

Incluez ceci dans votre "app.config":

 $httpProvider.interceptors.push('myHttpInterceptor');

Et ajoutez ce code:

app.factory('myHttpInterceptor', function ($q, $window,$rootScope) {
    $rootScope.ActiveAjaxConectionsWithouthNotifications = 0;
    var checker = function(parameters,status){
            //YOU CAN USE parameters.url TO IGNORE SOME URL
            if(status == "request"){
                $rootScope.ActiveAjaxConectionsWithouthNotifications+=1;
                $('#loading_view').show();
            }
            if(status == "response"){
                $rootScope.ActiveAjaxConectionsWithouthNotifications-=1;

            }
            if($rootScope.ActiveAjaxConectionsWithouthNotifications<=0){
                $rootScope.ActiveAjaxConectionsWithouthNotifications=0;
                $('#loading_view').hide();

            }


    };
return {
    'request': function(config) {
        checker(config,"request");
        return config;
    },
   'requestError': function(rejection) {
       checker(rejection.config,"request");
      return $q.reject(rejection);
    },
    'response': function(response) {
         checker(response.config,"response");
      return response;
    },
   'responseError': function(rejection) {
        checker(rejection.config,"response");
      return $q.reject(rejection);
    }
  };
});

2

Utilisez angular-busy :

Ajoutez cgBusy à votre application / module:

angular.module('your_app', ['cgBusy']);

Ajoutez votre promesse à scope:

function MyCtrl($http, User) {
  //using $http
  this.isBusy = $http.get('...');
  //if you have a User class based on $resource
  this.isBusy = User.$save();
}

Dans votre modèle html:

<div cg-busy="$ctrl.isBusy"></div>

2

En outre, il existe une belle démo qui montre comment utiliser l' Angularjsanimation dans votre projet.

Le lien est ici (voir le coin supérieur gauche) .

C'est une source ouverte. Voici le lien pour télécharger

Et voici le lien pour le tutoriel ;

Mon point est, allez-y et téléchargez les fichiers source, puis voyez comment ils ont implémenté le spinner. Ils auraient peut-être utilisé une approche un peu meilleure. Alors, consultez ce projet.


1

Voici un exemple d'intercepteur simple, je mets la souris en attente lorsque ajax démarre et je le règle sur auto lorsque ajax se termine.

$httpProvider.interceptors.push(function($document) {
return {
 'request': function(config) {
     // here ajax start
     // here we can for example add some class or show somethin
     $document.find("body").css("cursor","wait");

     return config;
  },

  'response': function(response) {
     // here ajax ends
     //here we should remove classes added on request start

     $document.find("body").css("cursor","auto");

     return response;
  }
};
});

Le code doit être ajouté dans la configuration de l'application app.config. J'ai montré comment changer la souris lors du chargement, mais là-dedans, il est possible d'afficher / masquer le contenu du chargeur, ou d'ajouter, de supprimer certaines classes css qui affichent le chargeur.

Interceptor fonctionnera à chaque appel ajax, donc pas besoin de créer des variables booléennes spéciales ($ scope.loading = true / false etc.) à chaque appel http.

Interceptor utilise construit dans angular jqLite https://docs.angularjs.org/api/ng/function/angular.element donc pas besoin de Jquery.


1

Créez une directive avec les attributs show et size (vous pouvez également en ajouter d'autres)

    app.directive('loader',function(){
    return {
    restrict:'EA',
    scope:{
        show : '@',
      size : '@'
    },
    template : '<div class="loader-container"><div class="loader" ng-if="show" ng-class="size"></div></div>'
  }
})

et en html, utilisez comme

 <loader show="{{loader1}}" size="sm"></loader>

Dans la variable show , passez true lorsqu'une promesse est en cours d'exécution et rendez-la false lorsque la demande est terminée. Démo active - Exemple de démonstration de la directive Angular Loader dans JsFiddle


0

J'ai construit un peu sur la réponse de @ DavidLin pour la simplifier - en supprimant toute dépendance à jQuery dans la directive. Je peux confirmer que cela fonctionne car je l'utilise dans une application de production

function AjaxLoadingOverlay($http) {

    return {
        restrict: 'A',
        link: function ($scope, $element, $attributes) {

            $scope.loadingOverlay = false;

            $scope.isLoading = function () {
                return $http.pendingRequests.length > 0;
            };

            $scope.$watch($scope.isLoading, function (isLoading) {
                $scope.loadingOverlay = isLoading;
            });
        }
    };
}   

J'utilise un ng-showau lieu d'un appel jQuery pour masquer / afficher le fichier <div>.

Voici le <div>que j'ai placé juste en dessous de la <body>balise d' ouverture :

<div ajax-loading-overlay class="loading-overlay" ng-show="loadingOverlay">
    <img src="Resources/Images/LoadingAnimation.gif" />
</div>

Et voici le CSS qui fournit la superposition pour bloquer l'interface utilisateur lors d'un appel $ http:

.loading-overlay {
    position: fixed;
    z-index: 999;
    height: 2em;
    width: 2em;
    overflow: show;
    margin: auto;
    top: 0;
    left: 0;
    bottom: 0;
    right: 0;
}

.loading-overlay:before {
    content: '';
    display: block;
    position: fixed;
    top: 0;
    left: 0;
    width: 100%;
    height: 100%;
    background-color: rgba(0,0,0,0.3);
}

/* :not(:required) hides these rules from IE9 and below */
.loading-overlay:not(:required) {
    font: 0/0 a;
    color: transparent;
    text-shadow: none;
    background-color: transparent;
    border: 0;
}

Le crédit CSS revient à @Steve Seeger's - son message: https://stackoverflow.com/a/35470281/335545


0

Vous pouvez ajouter une condition puis la modifier via le rootscope. Avant votre requête ajax, vous appelez simplement $ rootScope. $ Emit ('stopLoader');

angular.module('directive.loading', [])
        .directive('loading',   ['$http', '$rootScope',function ($http, $rootScope)
        {
            return {
                restrict: 'A',
                link: function (scope, elm, attrs)
                {
                    scope.isNoLoadingForced = false;
                    scope.isLoading = function () {
                        return $http.pendingRequests.length > 0 && scope.isNoLoadingForced;
                    };

                    $rootScope.$on('stopLoader', function(){
                        scope.isNoLoadingForced = true;
                    })

                    scope.$watch(scope.isLoading, function (v)
                    {
                        if(v){
                            elm.show();
                        }else{
                            elm.hide();
                        }
                    });
                }
            };

        }]);

Ce n'est certainement pas la meilleure solution mais cela fonctionnerait toujours.

En utilisant notre site, vous reconnaissez avoir lu et compris notre politique liée aux cookies et notre politique de confidentialité.
Licensed under cc by-sa 3.0 with attribution required.