(Angular-ui-router) Mostrar animación de carga durante el proceso de resolución

80

Esta es una pregunta de dos partes:

  1. Estoy usando la propiedad resolve dentro de $ stateProvider.state () para capturar ciertos datos del servidor antes de cargar el controlador. ¿Cómo haría para que se muestre una animación de carga durante este proceso?

  2. Tengo estados secundarios que también utilizan la propiedad resolve. El problema es que ui-router parece querer finalizar todas las resoluciones antes de cargar cualquier controlador. ¿Hay alguna forma de que los controladores principales se carguen una vez que se hayan resuelto sus resoluciones, sin tener que esperar a que todas las resoluciones secundarias? Una respuesta a esto probablemente también resolverá el primer problema.

Matt Way
fuente
4
¿Has resuelto alguna vez este problema?
Stefan Henze
3
@StefanHenze No, acabo de aceptar esto como un defecto arquitectónico de ui-router.
Matt Way
2
Hay una discusión sobre este problema exacto aquí: github.com/angular-ui/ui-router/issues/456
Johnny Oshika
6
@StefanHenze lol, sin juego de palabras ....
Dave

Respuestas:

71

EDITAR: Aquí hay una solución aún más fácil, probada y funcionando bien:

En mi controlador principal simplemente tengo

$scope.$on('$stateChangeStart', function(event, toState, toParams, fromState, fromParams) {
    if (toState.resolve) {
        $scope.showSpinner();
    }
});
$scope.$on('$stateChangeSuccess', function(event, toState, toParams, fromState, fromParams) {
    if (toState.resolve) {
        $scope.hideSpinner();
    }
});

Esto muestra la ruleta cuando estamos a punto de ir a un estado que tiene algo que resolver y lo oculta, cuando se completa el cambio de estado. Es posible que desee agregar algunas verificaciones de la jerarquía de estados (es decir, también mostrar la ruleta si un estado principal que se está cargando resuelve algo) pero esta solución funciona bien para mí.

Aquí está mi vieja sugerencia como referencia y como alternativa:

  1. En el controlador de su aplicación, escuche el stateChangeStartevento y verifique si está a punto de cambiar a un estado en el que desee mostrar una ruleta durante la resolución (consulte https://github.com/angular-ui/ui-router/wiki/Quick -Referencia # wiki-events-1 )

    $rootScope.$on('$stateChangeStart', function(event, toState, toParams, fromState, fromParams){
        if (toState.name == 'state.with.resolve') {
            $scope.showSpinner();  //this is a function you created to show the loading animation
        }
    })
    
  2. Cuando finalmente se llama a su controlador, puede ocultar la ruleta

    .controller('StateWithResolveCtrl', function($scope) {
        $scope.hideSpinner();
    })
    

También es posible que desee verificar los errores que puedan haber ocurrido durante la resolución escuchando el $stateChangeErrorevento y ocultando la animación mientras maneja el error.

Esto no está del todo limpio ya que distribuye la lógica de la ruleta entre los controladores, pero es una forma. Espero eso ayude.

Stefan Henze
fuente
1
if (toState.resolve)comprueba si la configuración de estado tiene un diccionario de resolución. En mi caso, esta es una estimación suficientemente buena de que el estado realiza llamadas de servicio asíncronas. Supongo que si hay un bloqueo de resolución, son las llamadas de servicio las que se resuelven. El código muestra y oculta la ruleta solo si se realizaron llamadas de servicio. Como describí anteriormente, es posible que este código deba perfeccionarse para verificar también los estados principales, según su caso de uso.
Stefan Henze
en la 'sugerencia anterior' a la que hace referencia $scopedentro de una llamada $rootScope. de donde $scopeviene el de aqui
pdeva
@pdeva, el controlador de eventos se definió dentro de algún controlador; ahí es donde también showSpinnerse definió la función.
Stefan Henze
No pude usar, toState.resolveasí que usé fromState.name !== toState.name. Aparte de eso, tu respuesta es fantástica, ¡bien hecho!
Jacob Hulse
22

Desarrollé la siguiente solución que funciona perfectamente para mí.

1. Agregue la siguiente aplicación.

app.run(function($rootScope){

    $rootScope
        .$on('$stateChangeStart', 
            function(event, toState, toParams, fromState, fromParams){ 
                $("#ui-view").html("");
                $(".page-loading").removeClass("hidden");
        });

    $rootScope
        .$on('$stateChangeSuccess',
            function(event, toState, toParams, fromState, fromParams){ 
                $(".page-loading").addClass("hidden");
        });

});

2. Coloque el indicador de carga justo encima de ui-view. Agregue id = "ui-view" a ui-view div.

<div class="page-loading">Loading...</div>
<div ui-view id="ui-view"></div>

3. agregue lo siguiente a su css

.hidden {
  display: none !important;
  visibility: hidden !important;
}

NOTA:

A. El código anterior mostrará el indicador de carga en dos casos 1) cuando la aplicación angular se carga por primera vez 2) cuando se cambia la vista.

B. Si no desea que se muestre el indicador cuando la aplicación angular se carga por primera vez (antes de que se cargue cualquier vista), agregue una clase oculta al div de carga como se muestra a continuación

<div class="page-loading hidden">Loading...</div>
Blesson José
fuente
15
Ese runcódigo no es el Angular Way. En lugar de manipular el DOM allí, establecidos como variables de ámbito loadingVisibley viewVisible, a continuación, en el HTML, imperativamente decir cuando los elementos son visibles u ocultos, como: <div class="page-loading" ng-show="viewVisible">Loading...</div>. Sin embargo, para vaciar la vista, puede requerir una directiva personalizada.
Matthias
2
NO se recomienda. Usar jQuery dentro de angularcontext es desagradable y está creando una dependencia no deseada entre su vista y controlador, ¡no es una buena práctica!
Silas Hansen
Me gusta esto pero ... ¿Es realmente correcto en relación con el DOM?
contributorpw
El problema de hacer esto 'de la manera angular' como sugiere @MatthiasDailey es que necesitarías duplicar la lógica de mostrar / ocultar por todas partes. Lo bueno de esta solución es que puede tener un método global para mostrar / ocultar el contenido o los cargadores. Si realmente desea hacer esto de la manera angular,
cree
1
Creo que ng-class sería un mejor enfoque para esto.
UserX
16

Descubrí que el uso de la barra de carga angular funcionó muy bien para resoluciones largas debido al acceso a la red.

Brian de Alwis
fuente
8

¿Qué tal agregar contenido al div que será llenado por ui-router una vez que las propiedades se hayan resuelto?

En tus index.html

<div ui-view class="container">
    Loading....
</div>

El usuario ahora verá "Cargando ..." mientras se resuelven las propiedades. Una vez que todo esté listo, el contenido será reemplazado por ui-router con el contenido de sus aplicaciones.

Wolfgang
fuente
7
Esto funcionaría si su aplicación puede tener un reemplazo completo de carga de la aplicación, pero se queda corto si desea construir 'cargando ...' jerárquicamente en su diseño (por ejemplo, mostrar parte de su aplicación, con otra parte mostrando carga ... ).
Matt Way
1
Sigue siendo una forma rápida / sucia / fácil de hacer que avance hacia cosas más importantes ... como acelerar sus resoluciones.
Brian Vanderbusch
3
Si está cargando varias plantillas en esa vista, lo ideal sería que lo hiciera. El mensaje "cargando ..." aparecería sólo por primera vez.
Yasser Shaikh
¿Es esto posible en Ionic? No pude usar esta forma en Ionic.
Anoop.PA
7

Utilizo un gif animado que solo muestro cuando $httptiene solicitudes pendientes.

En mi plantilla de página base, tengo una barra de navegación y un controlador de barra de navegación. La parte relevante del controlador se ve así:

controllers.controller('NavbarCtrl', ['$scope', '$http',
    function ($scope, $http) {
        $scope.hasPendingRequests = function () {
            return $http.pendingRequests.length > 0;
        };
    }]);

El código correspondiente en mi html es:

<span class="navbar-spinner" ng-show="hasPendingRequests()">
    <img src="/static/img/spinner.gif">
</span>

¡Espero que eso ayude!

joshvillbrandt
fuente
3
Como puede ver en mi pregunta, ¡no tengo acceso a los controladores hasta que se hayan finalizado todas las resoluciones (que es el problema)!
Matt Way
1
No debe usar la función para ng-show o ng-if, porque se llamarán en cada bucle de resumen. Disminuirá el rendimiento.
Vikas Gautam
4

Mi idea es recorrer el camino en el gráfico de estado entre los estados en transición $stateChangeStarty recopilar todas las vistas involucradas. Luego, cada ui-viewdirectiva observa si la vista correspondiente está involucrada en la transición y agrega 'ui-resolving'clase en su elemento.

La demostración de plunker presenta dos estados raíz: firsty second, el último tiene dos subestados second.sub1y second.sub2. El estado second.sub2también apunta a la footervista que pertenece a su abuelo.

aikoven
fuente
3

Este es un cargador para globalmente cuando la página navega entre cualquier estado (cualquier página), coloque app.js

.run(
    ['$rootScope',
        function($rootScope) {
            $rootScope.$on('$stateChangeStart', function(event, toState, toParams, fromState, fromParams) {
                $rootScope.preloader = true;
            })
            $rootScope.$on('$stateChangeSuccess', function(event, toState, toParams, fromState, fromParams) {
                $rootScope.preloader = false;
            })
        }
    ])

En html:

<div ng-show="preloader">Loading...</div>
manoz
fuente
¡Perfecto! ¡Gracias!
Brynner Ferreira
No podría ser más obvio ... tal vez explicar que ¿cómo pones algo en la vista de la interfaz de usuario mientras la carga está en progreso? Definir 2 variables es tan trivial
DDD
2

Prefiero usar una directiva para que coincida con cualquier actividad de carga, principalmente para mantener limpio mi código base

angular.module('$utilityElements', [])
.directive('loader',['$timeout','$rootScope', function($timeout, $rootScope) {
    return {
      restrict: 'A',
      template: '<div id="oneloader" class="hiddenload">loading...</div>',
      replace: true,
      compile: function (scope, element, attrs) {
        $timeout(function(){
          $rootScope
              .$on('$stateChangeStart',
                  function(event, toState, toParams, fromState, fromParams){
                      $("#oneloader").removeClass("hiddenload");
              });

          $rootScope
              .$on('$stateChangeSuccess',
                  function(event, toState, toParams, fromState, fromParams){
                      //add a little delay
                      $timeout(function(){
                        $("#oneloader").addClass("hiddenload");
                      },500)
              });
        }, 0);
      }
    }
  }]);
vorillaz
fuente
2

Desde entonces, el uso de $stateChangeStarty similares se ha desaprobado y reemplazado con Transition Hooks . Entonces, para la respuesta de Stefan Henze, la versión actualizada sería:

$transitions.onStart({}, function(transition) {
  if (transition.to().resolve) {
    $scope.showSpinner();
  }
});

$transitions.onSuccess({}, function(transition) {
  if (transition.to().resolve) {
    $scope.hideSpinner();
  }
});

Puede usar esto en su controlador principal. Recuerda inyectar$transitions -

.controller('parentController',['$transitions',function($transitions){...}]);

Además, tenga en cuenta que un resolveobjeto vacío todavía se representará transition.to().resolve == true, así que no deje un marcador resolvede posición vacío en la declaración de estado.

IB2DEV
fuente
1

Mi idea de usar la vista mientras uso resole en el enrutador está funcionando increíble. prueba esto.

//edit index.html file 
<ion-nav-view>
    <div ng-show="loadder" class="loddingSvg">
        <div class="svgImage"></div>
    </div>
</ion-nav-view>

// css file

.loddingSvg {
    height: 100%;
    background-color: rgba(0, 0, 0, 0.1);
    position: absolute;
    z-index: 99;
    left: 0;
    right: 0;
}

.svgImage {
    background: url(../img/default.svg) no-repeat;
    position: relative;
    z-index: 99;
    height: 65px;
    width: 65px;
    background-size: 56px;
    top: 50%;
    margin: 0 auto;
}

// edit app.js

 .run(function($ionicPush, $rootScope, $ionicPlatform) {




        $rootScope.$on('$stateChangeStart',
            function(event, toState, toParams, fromState, fromParams) {
                $rootScope.loadder = true;
            });

        $rootScope.$on('$stateChangeSuccess',
            function(event, toState, toParams, fromState, fromParams) {
                $rootScope.loadder = false;
            });

});
koneri ranjith kumar
fuente
0

Si alguien está usando ngRoute, esperando resolveantes de cargar la siguiente vista y usando angular-bootstrap-uifor ui, puede hacer lo siguiente :

app.config([
  "$routeProvider", "$locationProvider", function($routeProvider, $locationProvider) {
    return $routeProvider.when("/seasons/:seasonId", {
      templateUrl: "season-manage.html",
      controller: "SeasonManageController",
      resolve: {
        season: [
          "$route", "$q", "$http", "$modal", function($route, $q, $http, $modal) {
            var modal, promise, seasonId;
            modal = $modal.open({
              backdrop: "static",
              template: "<div>\n  <div class=\"modal-header\">\n    <h3 class=\"modal-title\">\n      Loading...\n    </h3>\n  </div>\n  <div class=\"modal-body\">\n    <progressbar\n      class=\"progress-striped active\"\n      value=\"'100'\">\n    </progressbar>\n  </div>\n</div>",
              keyboard: false,
              size: "lg"
            });
            promise = $q.defer();
            seasonId = $route.current.params.seasonId;
            $http.get("/api/match/seasons/" + seasonId).success(function(data) {
              modal.close();
              promise.resolve(data);
            }).error(function(data) {
              modal.close();
              promise.reject(data);
            });

            return promise.promise;
          }
        ]
      }
    });
  }
]);
Blaskovicz
fuente