Pantalla de carga de Angularjs en una solicitud ajax

117

Usando Angularjs, necesito mostrar una pantalla de carga (una ruleta simple) hasta que se complete la solicitud ajax. Sugiera cualquier idea con un fragmento de código.

Badhrinath Canessane
fuente
4
eche un vistazo a stackoverflow.com/questions/15033195/…
Ajay Beniwal
La mejor práctica es usar, $httpProvider.interceptorsya que puede manejar cuándo comienza la solicitud ajax y cuándo termina. Es por eso $watchque ya no es necesario para detectar cuándo comienza y finaliza la llamada ajax.
Frank Myat Jue
¿ Qué tal si revisa mi disparador angular httpshooter de fábrica , le brinda un mejor control de los cargadores y la IU de congelación?
Siddharth

Respuestas:

210

En lugar de configurar una variable de alcance para indicar el estado de carga de datos, es mejor tener una directiva que haga todo por usted:

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 esta directiva, todo lo que necesita hacer es darle a cualquier elemento de animación de carga un atributo de 'carga':

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

Puede tener varios marcadores de carga en la página. dónde y cómo distribuir esos elementos giratorios depende de usted y la directiva simplemente lo activará / desactivará automáticamente.

David Lin
fuente
aquí se llama dos veces a la directiva "loading", con $ location.path (). Primero con la página actual y luego con la página de destino. ¿Cuál debería ser la razón?
Sanjay D
Excelente. Y también, puede usar jquery toggle on watch: elm.toggle (v). Tengo un monitor de sesión de $ timeout y, para mí, necesito mostrar el control giratorio solo después de medio segundo. Implementaré un CSS con transición para mostrar más lentamente la ruleta.
Joao Polo
7
Si obtiene el error "elm.show () no es una función", debe agregar jquery antes de cargar angular.
morpheus05
La forma en que haces scope.watch no funciona para mí. Tuve que hacerlo de la forma en que lo hace jzm (usando '' alrededor del nombre y omitiendo el prefijo de alcance). ¿Alguna idea de por qué?
KingOfHypocrites
3
¿Qué sucede si necesito async. cargar varias áreas diferentes?
Vadim Ferderer
56

He aquí un ejemplo. Utiliza el método simple 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;
    });
  }

Por supuesto, puede mover el código html del cuadro de carga a una directiva y luego usar $ watch en $ scope.loading. En ese caso:

HTML :

<loading></loading>

DIRECTIVA 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

mnsr
fuente
La forma en que haces scope.watch funciona para mí mientras que el enfoque de respuesta aceptado no.
KingOfHypocrites
@jzm Qué gran solución 😊, desafortunadamente esta solución no funciona ui-router, no sé por qué
Lunes
Hice esto en uno de mis proyectos funcionó bien, en otro proyecto creo que debido a una solicitud pesada de ajax loading=trueno se configura
alamnaryab
21

Utilizo ngProgress para esto.

Agregue 'ngProgress' a sus dependencias una vez que haya incluido los archivos script / css en su HTML. Una vez que lo hagas, puedes configurar algo como esto, que se activará cuando se detecte un cambio de ruta.

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

Para las solicitudes AJAX, puede hacer algo como esto:

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

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

Solo recuerde agregar 'ngProgress' a las dependencias de los controladores antes de hacerlo. Y si está haciendo varias solicitudes AJAX, use una variable incremental en el alcance de la aplicación principal para realizar un seguimiento de cuándo han finalizado sus solicitudes AJAX antes de llamar a 'ngProgress.complete ();'.

Nej Kutcharian
fuente
15

el uso de SavedRequests no es correcto porque, como se menciona en la documentación de Angular, esta propiedad está destinada principalmente a ser utilizada con fines de depuración.

Lo que recomiendo es usar un interceptor para saber si hay alguna llamada Async activa.

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

y luego verifique si activeCalls es cero o no en la directiva a través de 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();
                }
            });
        }
    };
});
Mahdi K.
fuente
7

La mejor manera de hacer esto es usar interceptores de respuesta junto con una directiva personalizada . Y el proceso se puede mejorar aún más usando el mecanismo pub / sub usando los métodos $ rootScope. $ Broadcast & $ rootScope. $ On .

Como todo el proceso está documentado en un artículo de blog bien escrito , no voy a repetirlo aquí de nuevo. Consulte ese artículo para obtener la implementación que necesita.

MN Islam Shihan
fuente
4

En referencia a esta respuesta

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

Para mí es la mejor solución, pero hay una forma de evitar el uso de 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>

Necesita reemplazar $ (elemento) .show (); y (elemento) .show (); con $ scope.loadingStatus = 'true'; y $ scope.loadingStatus = 'false';

Entonces, debe usar esta variable para establecer el atributo ng-show del elemento.

Karsus
fuente
4

Implementación de mecanografiado y angular

directiva

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

Alcance

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

Modelo

<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;
}
Código-EZ
fuente
3

Si está utilizando Restangular (que es increíble), puede crear una animación durante las llamadas a la API. Aquí está mi solución. Agregue un interceptor de respuesta y un interceptor de solicitud que envíe una transmisión de alcance radicular. Luego crea una directiva para escuchar esa respuesta y solicitud:

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

Aquí está la directiva:

        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;
        });
      }
    };
  })
;
Enkode
fuente
Cualquier respuesta de la API matará la animación. Debe almacenar información adicional sobre solicitud - respuesta y reaccionar solo para esa respuesta que se inicializó mediante solicitud.
Krzysztof Safjanowski
3

Incluya esto en su "app.config":

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

Y agrega este código:

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);
    }
  };
});
BratisLatas
fuente
2

Utilice angular-ocupado :

Agregue cgBusy a su aplicación / módulo:

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

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

En su plantilla html:

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

Además, hay una buena demostración que muestra cómo puede usar la Angularjsanimación en su proyecto.

El enlace está aquí (vea la esquina superior izquierda) .

Es una fuente abierta. Aquí está el enlace para descargar

Y aquí está el enlace para el tutorial ;

Mi punto es, continúe y descargue los archivos fuente y luego vea cómo han implementado el spinner. Podrían haber utilizado un enfoque un poco mejor. Entonces, mira este proyecto.

Idrees Khan
fuente
1

Aquí, un simple ejemplo de interceptor, configuro el mouse en espera cuando se inicia ajax y lo configuro en automático cuando finaliza 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;
  }
};
});

El código debe agregarse en la configuración de la aplicación app.config . Mostré cómo cambiar el mouse en el estado de carga, pero allí es posible mostrar / ocultar cualquier contenido del cargador, o agregar, eliminar algunas clases css que muestran el cargador.

El interceptor se ejecutará en cada llamada ajax, por lo que no es necesario crear variables booleanas especiales ($ scope.loading = true / false, etc.) en cada llamada http.

Interceptor está utilizando jqLite angular compilado https://docs.angularjs.org/api/ng/function/angular.element, por lo que no se necesita Jquery.

Maciej Sikora
fuente
1

Cree una directiva con los atributos de espectáculo y tamaño (también puede agregar más)

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

y en html usar como

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

En la variable show , pase verdadero cuando se esté ejecutando cualquier promesa y hágalo falso cuando se complete la solicitud. Demostración activa: demostración de ejemplo de la directiva Angular Loader en JsFiddle

Partha Roy
fuente
0

Me basé un poco en la respuesta de @ DavidLin para simplificarla, eliminando cualquier dependencia de jQuery en la directiva. Puedo confirmar que esto funciona ya que lo uso en una aplicación de producción.

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

Utilizo una ng-showllamada a jQuery en lugar de una para ocultar / mostrar el <div>.

Aquí está el <div>que coloqué justo debajo de la <body>etiqueta de apertura :

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

Y aquí está el CSS que proporciona la superposición para bloquear la interfaz de usuario mientras se realiza una llamada $ 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;
}

El crédito de CSS es para @Steve Seeger, su publicación: https://stackoverflow.com/a/35470281/335545

Berna
fuente
0

Puede agregar una condición y luego cambiarla a través del raicescopio. Antes de su solicitud ajax, simplemente llame a $ 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();
                        }
                    });
                }
            };

        }]);

Definitivamente, esta no es la mejor solución, pero aún funcionaría.

User_3535
fuente