Schermata di caricamento di Angularjs su richiesta ajax


117

Utilizzando Angularjs, ho bisogno di mostrare una schermata di caricamento (un semplice spinner) fino a quando la richiesta ajax non è completa. Si prega di suggerire qualsiasi idea con uno snippet di codice.




La migliore pratica è usare $httpProvider.interceptorspoiché può gestire quando la richiesta ajax inizia e quando finisce. Ecco perché $watchnon è più necessario rilevare l'inizio e la fine di una chiamata ajax.
Frank Myat giovedì

che ne dici di controllare il mio sparatutto angolare di fabbrica , ti dà un controllo migliore per caricatori e congelamento dell'interfaccia utente
Siddharth

Risposte:


210

Invece di impostare una variabile di ambito per indicare lo stato di caricamento dei dati, è meglio che una direttiva faccia tutto per te:

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();
                    }
                });
            }
        };

    }]);

Con questa direttiva, tutto ciò che devi fare è assegnare a qualsiasi elemento di animazione di caricamento un attributo 'loading':

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

Puoi avere più filatori di caricamento sulla pagina. dove e come disporre questi filatori dipende da te e la direttiva semplicemente lo accenderà / spegnerà automaticamente.


qui la direttiva "caricamento" viene chiamata due volte, con $ location.path (). Prima con la pagina corrente e poi con la pagina di destinazione. Quale dovrebbe essere il motivo?
Sanjay D

Eccellente. Inoltre, puoi usare jquery toggle sull'orologio: elm.toggle (v). Ho un monitor di sessione di $ timeout e, per me, devo mostrare lo spinner solo dopo mezzo secondo. Implementerò un CSS con transizione per mostrare più lentamente lo spinner.
Joao Polo

7
Se ottieni l'errore "elm.show () non è una funzione", devi aggiungere jquery prima di caricare angular.
morpheus05

Il modo in cui fai scope.watch non funziona per me. Ho dovuto farlo nel modo in cui lo fa jzm (usando "" intorno al nome e omettendo il prefisso dell'ambito). Qualche idea sul perché?
KingOfHypocrites

3
E se avessi bisogno di eseguire l'asincronizzazione. caricare più aree diverse?
Vadim Ferderer

56

Ecco un esempio. Usa il semplice metodo ng-show con un bool.

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;
    });
  }

Ovviamente puoi spostare il codice html della casella di caricamento in una direttiva, quindi utilizzare $ watch su $ scope.loading. In quale caso:

HTML :

<loading></loading>

DIRETTIVA ANGOLARI :

  .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


Il modo in cui fai scope.watch funziona per me, mentre l'approccio di risposta accettato no.
KingOfHypocrites

@jzm Un'ottima soluzione 😊, sfortunatamente questa soluzione non funziona ui-router, non so perché
Mo.

L'ho fatto in uno dei miei progetti che ha funzionato bene, in un altro progetto penso che a causa della pesante richiesta ajax loading=truenon venga impostato
alamnaryab

21

Uso ngProgress per questo.

Aggiungi "ngProgress" alle tue dipendenze dopo aver incluso i file script / css nel tuo HTML. Una volta che lo fai, puoi impostare qualcosa di simile, che si attiverà quando viene rilevato un cambio di percorso.

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

Per le richieste AJAX puoi fare qualcosa del genere:

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

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

Ricorda solo di aggiungere "ngProgress" alle dipendenze dei controller prima di farlo. E se stai eseguendo più richieste AJAX, utilizza una variabile incrementale nell'ambito dell'app principale per tenere traccia del termine delle tue richieste AJAX prima di chiamare 'ngProgress.complete ();'.


15

l'utilizzo di pendingRequests non è corretto perché, come menzionato nella documentazione di Angular, questa proprietà è principalmente pensata per essere utilizzata per scopi di debug.

Quello che raccomando è di utilizzare un intercettore per sapere se c'è qualche chiamata asincrona attiva.

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;
            }
        };
    });
}]);

e poi controlla se activeCalls è zero o meno nella direttiva tramite $ 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

Il modo migliore per farlo è utilizzare intercettori di risposta insieme alla direttiva personalizzata . E il processo può essere ulteriormente migliorato usando il meccanismo pub / sub usando i metodi $ rootScope. $ Broadcast e $ rootScope. $ On .

Poiché l'intero processo è documentato in un articolo di blog ben scritto , non lo ripeterò qui di nuovo. Fare riferimento a quell'articolo per trovare l'implementazione necessaria.


4

In riferimento a questa risposta

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

Per me è la soluzione migliore ma c'è un modo per evitare di usare 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>

Devi sostituire $ (element) .show (); e (elemento) .show (); con $ scope.loadingStatus = 'true'; e $ scope.loadingStatus = 'false';

Quindi, è necessario utilizzare questa variabile per impostare l'attributo ng-show dell'elemento.


4

Dattiloscritto e implementazione angolare

direttiva

((): 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;
    }
})();

Scopo

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

Modello

<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

Se stai usando Restangular (che è fantastico) puoi creare un'animazione durante le chiamate API. Ecco la mia soluzione. Aggiungere un intercettore di risposta e un intercettore di richiesta che invia una trasmissione dell'ambito radice. Quindi crea una direttiva per ascoltare quella risposta e richiesta:

         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;
      });
    });
  }]);

Ecco la direttiva:

        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;
        });
      }
    };
  })
;

Qualsiasi risposta API interromperà l'animazione. È necessario memorizzare ulteriori informazioni sulla richiesta - risposta e reagire solo per quella risposta inizializzata su richiesta.
Krzysztof Safjanowski

3

Includilo nel tuo "app.config":

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

E aggiungi questo codice:

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

Usa angolare occupato :

Aggiungi cgBusy alla tua app / modulo:

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

Aggiungi la tua promessa a scope:

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

Nel tuo modello html:

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

2

Inoltre, c'è una bella demo che mostra come puoi usare l' Angularjsanimazione nel tuo progetto.

Il collegamento è qui (vedere l'angolo in alto a sinistra) .

È un open source. Ecco il link per il download

Ed ecco il link per il tutorial ;

Il punto è, vai avanti e scarica i file sorgente e poi guarda come hanno implementato lo spinner. Avrebbero potuto usare un approccio leggermente migliore. Quindi, controlla questo progetto.


1

Qui un semplice esempio di interceptor, ho impostato il mouse in attesa all'avvio di ajax e l'ho impostato su auto quando termina ajax.

$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;
  }
};
});

Il codice deve essere aggiunto nella configurazione dell'applicazione app.config. Ho mostrato come cambiare lo stato del mouse durante il caricamento, ma lì è possibile mostrare / nascondere qualsiasi contenuto del caricatore o aggiungere, rimuovere alcune classi css che mostrano il caricatore.

Interceptor verrà eseguito su ogni chiamata ajax, quindi non è necessario creare variabili booleane speciali ($ scope.loading = true / false ecc.) Su ogni chiamata http.

Interceptor sta usando costruito in jqLite angolare https://docs.angularjs.org/api/ng/function/angular.element, quindi non è necessario Jquery.


1

Crea una direttiva con gli attributi spettacolo e taglia (puoi anche aggiungerne altre)

    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>'
  }
})

e in html usa come

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

Nella variabile show passa true quando è in esecuzione una promessa e rendila falsa quando la richiesta è completata. Demo attiva - Demo di esempio della direttiva Angular Loader in JsFiddle


0

Ho costruito un po 'sulla risposta di @ DavidLin per semplificarlo, rimuovendo qualsiasi dipendenza da jQuery nella direttiva. Posso confermare che funziona poiché lo uso in un'applicazione di produzione

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;
            });
        }
    };
}   

Uso un ng-showinvece di una chiamata jQuery per nascondere / mostrare il file <div>.

Ecco quello <div>che ho posizionato appena sotto il <body>tag di apertura :

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

Ed ecco il CSS che fornisce l'overlay per bloccare l'interfaccia utente mentre viene effettuata una chiamata $ 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;
}

Il credito CSS va a @Steve Seeger - il suo post: https://stackoverflow.com/a/35470281/335545


0

È possibile aggiungere una condizione e quindi modificarla tramite il rootcope. Prima della tua richiesta ajax, chiama semplicemente $ 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();
                        }
                    });
                }
            };

        }]);

Questa non è sicuramente la soluzione migliore, ma funzionerebbe comunque.

Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.