Retrasar el cambio de ruta de AngularJS hasta que se cargue el modelo para evitar el parpadeo

12 minutos de lectura

Retrasar el cambio de ruta de AngularJS hasta que se
misko hevery

Me pregunto si hay una forma (similar a Gmail) para que AngularJS retraso en mostrar una nueva ruta hasta después de que se haya obtenido cada modelo y sus datos utilizando sus respectivos servicios.

Por ejemplo, si hubiera un ProjectsController que enumeró todos los proyectos y project_index.html cual era la plantilla que mostraba estos Proyectos, Project.query() se recuperaría por completo antes de mostrar la nueva página.

Hasta entonces, la página anterior seguiría mostrándose (por ejemplo, si estuviera navegando por otra página y luego decidiera ver este índice de Proyecto).

Retrasar el cambio de ruta de AngularJS hasta que se
misko hevery

$rutaProveedor resolver La propiedad permite retrasar el cambio de ruta hasta que se carguen los datos.

Primero defina una ruta con resolve atributo como este.

angular.module('phonecat', ['phonecatFilters', 'phonecatServices', 'phonecatDirectives']).
  config(['$routeProvider', function($routeProvider) {
    $routeProvider.
      when('/phones', {
        templateUrl: 'partials/phone-list.html', 
        controller: PhoneListCtrl, 
        resolve: PhoneListCtrl.resolve}).
      when('/phones/:phoneId', {
        templateUrl: 'partials/phone-detail.html', 
        controller: PhoneDetailCtrl, 
        resolve: PhoneDetailCtrl.resolve}).
      otherwise({redirectTo: '/phones'});
}]);

notar que el resolve la propiedad se define en la ruta.

function PhoneListCtrl($scope, phones) {
  $scope.phones = phones;
  $scope.orderProp = 'age';
}

PhoneListCtrl.resolve = {
  phones: function(Phone, $q) {
    // see: https://groups.google.com/forum/?fromgroups=#!topic/angular/DGf7yyD4Oc4
    var deferred = $q.defer();
    Phone.query(function(successData) {
            deferred.resolve(successData); 
    }, function(errorData) {
            deferred.reject(); // you could optionally pass error data here
    });
    return deferred.promise;
  },
  delay: function($q, $defer) {
    var delay = $q.defer();
    $defer(delay.resolve, 1000);
    return delay.promise;
  }
}

Tenga en cuenta que la definición del controlador contiene un objeto de resolución que declara cosas que deberían estar disponibles para el constructor del controlador. Aquí el phones se inyecta en el controlador y se define en el resolve propiedad.

los resolve.phones La función es responsable de devolver una promesa. Se recopilan todas las promesas y el cambio de ruta se retrasa hasta que se resuelven todas las promesas.

demostración de trabajo: http://mhevery.github.com/angular-phonecat/app/#/teléfonos
Fuente: https://github.com/mhevery/angular-phonecat/commit/ba33d3ec2d01b70eb5d3d531619bf90153496831

  • @MiskoHevery: ¿qué sucede si sus controladores están dentro de un módulo y se definen como una cadena en lugar de una función? ¿Cómo podría configurar el atributo de resolución como lo hace?

    – aar0n

    3 oct 2012 a las 22:29

  • ¿Cómo se usa esto en angular.controller() definiciones de controlador de tipo? En el $routeProvider cosas, pensé que tenías que usar nombres de cadenas de controladores.

    – Ben Lesh

    9 de noviembre de 2012 a las 14:11

  • ¿Algún ejemplo usando angular.controller() y con la última versión de AngularJS?

    – Laurent

    7 de marzo de 2013 a las 6:13

  • @blesh, cuando usas angular.controller()puede asignar el resultado de esta función a una variable (var MyCtrl = angular.controller(...)) y luego trabajar con eso más (MyCtrl.loadData = function(){..}). Mira el video de egghead, el código se muestra allí de inmediato: egghead.io/video/0uvAseNXDr0

    – petrkotek

    21 de abril de 2013 a las 2:33

  • Todavía me gustaría una buena manera de hacerlo sin tener que colocar su controlador en un global. No quiero ensuciar con globales por todos lados. Puede hacerlo con una constante, pero sería bueno poder poner la función de resolución en el controlador, no en otro lugar.

    – Erik Honn

    13 de septiembre de 2013 a las 14:22


Aquí hay un ejemplo de trabajo mínimo que funciona para Angular 1.0.2

Plantilla:

<script type="text/ng-template" id="/editor-tpl.html">
    Editor Template {{datasets}}
</script>

<div ng-view>

</div>

JavaScript:

function MyCtrl($scope, datasets) {    
    $scope.datasets = datasets;
}

MyCtrl.resolve = {
    datasets : function($q, $http) {
        var deferred = $q.defer();

        $http({method: 'GET', url: '/someUrl'})
            .success(function(data) {
                deferred.resolve(data)
            })
            .error(function(data){
                //actually you'd want deffered.reject(data) here
                //but to show what would happen on success..
                deferred.resolve("error value");
            });

        return deferred.promise;
    }
};

var myApp = angular.module('myApp', [], function($routeProvider) {
    $routeProvider.when("https://stackoverflow.com/", {
        templateUrl: '/editor-tpl.html',
        controller: MyCtrl,
        resolve: MyCtrl.resolve
    });
});​
​

http://jsfiddle.net/dTJ9N/3/

Versión simplificada:

Dado que $http() ya devuelve una promesa (también conocida como diferida), en realidad no necesitamos crear la nuestra. Entonces podemos simplificar MyCtrl. resolver:

MyCtrl.resolve = {
    datasets : function($http) {
        return $http({
            method: 'GET', 
            url: 'http://fiddle.jshell.net/'
        });
    }
};

El resultado de $http() contiene datos, estado, encabezados y configuración objetos, por lo que debemos cambiar el cuerpo de MyCtrl a:

$scope.datasets = datasets.data;

http://jsfiddle.net/dTJ9N/5/

  • Estoy tratando de hacer algo como esto, pero tengo problemas para inyectar ‘conjuntos de datos’ ya que no está definido. ¿Alguna idea?

    – Rob Bygrave

    08/01/2013 a las 10:50

  • Hola mb21, creo que podrías ayudarme con esta pregunta: stackoverflow.com/questions/14271713/…

    – juguete de cuerda

    11 de enero de 2013 a las 4:33

  • ¿Alguien podría ayudarme a convertir esta respuesta al formato app.controller(‘MyCtrl’)? jsfiddle.net/5usya/1 no funcionó para mí

    – usuario1071182

    15 de abril de 2013 a las 3:03

  • me sale un error: Unknown provider: datasetsProvider <- datasets

    – chovy

    25 de noviembre de 2013 a las 6:32

  • Puede simplificar su respuesta reemplazando conjuntos de datos con esto: function($http) { return $http({method: 'GET', url: '/someUrl'}) .then( function(data){ return data;}, function(reason){return 'error value';} ); }

    –Morteza Tourani

    8 mayo 2016 a las 20:29

Retrasar el cambio de ruta de AngularJS hasta que se
tonto

Veo que algunas personas preguntan cómo hacer esto usando el método angular.controller con inyección de dependencia amigable con la minificación. Como acabo de hacer que esto funcione, me sentí obligado a volver y ayudar. Aquí está mi solución (adoptada de la pregunta original y la respuesta de Misko):

angular.module('phonecat', ['phonecatFilters', 'phonecatServices', 'phonecatDirectives']).
  config(['$routeProvider', function($routeProvider) {
    $routeProvider.
      when('/phones', {
        templateUrl: 'partials/phone-list.html', 
        controller: PhoneListCtrl, 
        resolve: { 
            phones: ["Phone", "$q", function(Phone, $q) {
                var deferred = $q.defer();
                Phone.query(function(successData) {
                  deferred.resolve(successData); 
                }, function(errorData) {
                  deferred.reject(); // you could optionally pass error data here
                });
                return deferred.promise;
             ]
            },
            delay: ["$q","$defer", function($q, $defer) {
               var delay = $q.defer();
               $defer(delay.resolve, 1000);
               return delay.promise;
              }
            ]
        },

        }).
      when('/phones/:phoneId', {
        templateUrl: 'partials/phone-detail.html', 
        controller: PhoneDetailCtrl, 
        resolve: PhoneDetailCtrl.resolve}).
      otherwise({redirectTo: '/phones'});
}]);

angular.controller("PhoneListCtrl", [ "$scope", "phones", ($scope, phones) {
  $scope.phones = phones;
  $scope.orderProp = 'age';
}]);

Dado que este código se deriva de la pregunta/respuesta más popular, no se ha probado, pero debería enviarlo en la dirección correcta si ya comprende cómo hacer un código angular compatible con la minificación. La única parte que mi propio código no requería era una inyección de “Teléfono” en la función de resolución para “teléfonos”, ni usé ningún objeto de “retraso”.

tambien te recomiendo este video de youtube http://www.youtube.com/watch?v=P6KITGRQujQ&list=UUKW92i7iQFuNILqQOUOCrFw&index=4&feature=plcp que me ayudó bastante

Si le interesa, he decidido pegar también mi propio código (escrito en script de café) para que pueda ver cómo lo hice funcionar.

FYI, de antemano uso un controlador genérico que me ayuda a hacer CRUD en varios modelos:

appModule.config ['$routeProvider', ($routeProvider) ->
  genericControllers = ["boards","teachers","classrooms","students"]
  for controllerName in genericControllers
    $routeProvider
      .when "/#{controllerName}/",
        action: 'confirmLogin'
        controller: 'GenericController'
        controllerName: controllerName
        templateUrl: "/static/templates/#{controllerName}.html"
        resolve:
          items : ["$q", "$route", "$http", ($q, $route, $http) ->
             deferred = $q.defer()
             controllerName = $route.current.controllerName
             $http(
               method: "GET"
               url: "/api/#{controllerName}/"
             )
             .success (response) ->
               deferred.resolve(response.payload)
             .error (response) ->
               deferred.reject(response.message)

             return deferred.promise
          ]

  $routeProvider
    .otherwise
      redirectTo: "https://stackoverflow.com/"
      action: 'checkStatus'
]

appModule.controller "GenericController", ["$scope", "$route", "$http", "$cookies", "items", ($scope, $route, $http, $cookies, items) ->

  $scope.items = items
      #etc ....
    ]

  • ¿Deduzco correctamente de su ejemplo y mis intentos fallidos que ahora es imposible hacer referencia a un resolve función en el controlador, en versiones recientes de Angular? Entonces, ¿debe declararse directamente en la configuración como está aquí?

    – XML

    6 de agosto de 2013 a las 23:26

  • @XMLilley Estoy bastante seguro de que ese es el caso. Este ejemplo era de 1.1.2 cuando lo escribí, creo. No vi ninguna documentación sobre cómo poner resolver dentro de un controlador

    – tonto

    07/08/2013 a las 14:25

  • Genial, gracias. Hay muchos ejemplos de hacerlo en SO (como los dos primeros aquí), pero todos son de 2012 y principios de 2013. Es un enfoque elegante, pero parece estar obsoleto. La alternativa más limpia ahora parece estar escribiendo servicios individuales que son objetos de promesa.

    – XML

    7 de agosto de 2013 a las 15:04

  • Gracias esto funcionó para mí. Para cualquier otra persona que obtenga errores sobre undefined $defer servicio, tenga en cuenta que en la versión 1.5.7 de AngularJS, desea utilizar $timeout en lugar de.

    – racl101

    22 de julio de 2016 a las 20:08

1646759652 480 Retrasar el cambio de ruta de AngularJS hasta que se
Max Hoffman

este compromisoque es parte de la versión 1.1.5 y superior, expone el $promise objeto de $resource. Las versiones de ngResource que incluyen este compromiso permiten resolver recursos como este:

$rutaProveedor

resolve: {
    data: function(Resource) {
        return Resource.get().$promise;
    }
}

controlador

app.controller('ResourceCtrl', ['$scope', 'data', function($scope, data) {

    $scope.data = data;

}]);

Retrasar el cambio de ruta de AngularJS hasta que se
nulo

Este fragmento es inyección de dependencia amigable (incluso lo uso en combinación con ngmin y afear) y es más elegante impulsado por dominio solución basada.

El siguiente ejemplo registra un Teléfono recurso y un constante rutas telefónicas, que contiene toda su información de enrutamiento para ese dominio (teléfono). Algo que no me gustó en la respuesta proporcionada fue la ubicación de la resolver lógica — la principal el módulo no debería saber nada o preocuparse por la forma en que se proporcionan los argumentos de recursos al controlador. De esta manera la lógica permanece en el mismo dominio.

Nota: si está utilizando ngmin (y si no lo es: debería) solo tiene que escribir las funciones de resolución con la convención de matriz DI.

angular.module('myApp').factory('Phone',function ($resource) {
  return $resource('/api/phone/:id', {id: '@id'});
}).constant('phoneRoutes', {
    '/phone': {
      templateUrl: 'app/phone/index.tmpl.html',
      controller: 'PhoneIndexController'
    },
    '/phone/create': {
      templateUrl: 'app/phone/edit.tmpl.html',
      controller: 'PhoneEditController',
      resolve: {
        phone: ['$route', 'Phone', function ($route, Phone) {
          return new Phone();
        }]
      }
    },
    '/phone/edit/:id': {
      templateUrl: 'app/phone/edit.tmpl.html',
      controller: 'PhoneEditController',
      resolve: {
        form: ['$route', 'Phone', function ($route, Phone) {
          return Phone.get({ id: $route.current.params.id }).$promise;
        }]
      }
    }
  });

La siguiente pieza es inyectar los datos de enrutamiento cuando el módulo está en el estado de configuración y aplicarlos al $rutaProveedor.

angular.module('myApp').config(function ($routeProvider, 
                                         phoneRoutes, 
                                         /* ... otherRoutes ... */) {

  $routeProvider.when("https://stackoverflow.com/", { templateUrl: 'app/main/index.tmpl.html' });

  // Loop through all paths provided by the injected route data.

  angular.forEach(phoneRoutes, function(routeData, path) {
    $routeProvider.when(path, routeData);
  });

  $routeProvider.otherwise({ redirectTo: "https://stackoverflow.com/" });

});

Probar la configuración de la ruta con esta configuración también es bastante fácil:

describe('phoneRoutes', function() {

  it('should match route configuration', function() {

    module('myApp');

    // Mock the Phone resource
    function PhoneMock() {}
    PhoneMock.get = function() { return {}; };

    module(function($provide) {
      $provide.value('Phone', FormMock);
    });

    inject(function($route, $location, $rootScope, phoneRoutes) {
      angular.forEach(phoneRoutes, function (routeData, path) {

        $location.path(path);
        $rootScope.$digest();

        expect($route.current.templateUrl).toBe(routeData.templateUrl);
        expect($route.current.controller).toBe(routeData.controller);
      });
    });
  });
});

Puedes verlo en todo su esplendor en mi último (próximo) experimento. Aunque este método funciona bien para mí, realmente me pregunto por qué $injector no está retrasando la construcción de cualquier cosa cuando detecta inyección de cualquier cosa eso es un promesa objeto; Haría las cosas muuuuuuucho más fáciles.

Editar: usó Angular v1.2 (rc2)

  • Esta excelente respuesta parece mucho más en línea con la filosofía “Angular” (encapsulación, etc.). Todos deberíamos hacer un esfuerzo consciente para evitar que la lógica se deslice por todo el código base como kudzu.

    – zakdanzas

    22/10/2013 a las 23:06

  • I really wonder why the $injector isn't delaying construction of anything when it detects injection of anything that is a promise object Supongo que omitieron esta funcionalidad porque podría fomentar patrones de diseño que afectan negativamente la capacidad de respuesta de las aplicaciones. La aplicación ideal en su mente es una que sea realmente asíncrona, por lo que la resolución debería ser un caso límite.

    – zakdanzas

    22/10/2013 a las 23:09

1646759653 85 Retrasar el cambio de ruta de AngularJS hasta que se
jpsimons

Retrasar la visualización de la ruta seguramente conducirá a un enredo asíncrono… ¿por qué no simplemente rastrear el estado de carga de su entidad principal y usarlo en la vista? Por ejemplo, en su controlador, puede usar las devoluciones de llamada de éxito y error en ngResource:

$scope.httpStatus = 0; // in progress
$scope.projects = $resource.query('/projects', function() {
    $scope.httpStatus = 200;
  }, function(response) {
    $scope.httpStatus = response.status;
  });

Luego, en la vista, podrías hacer lo que sea:

<div ng-show="httpStatus == 0">
    Loading
</div>
<div ng-show="httpStatus == 200">
    Real stuff
    <div ng-repeat="project in projects">
         ...
    </div>
</div>
<div ng-show="httpStatus >= 400">
    Error, not found, etc. Could distinguish 4xx not found from 
    5xx server error even.
</div>

  • Esta excelente respuesta parece mucho más en línea con la filosofía “Angular” (encapsulación, etc.). Todos deberíamos hacer un esfuerzo consciente para evitar que la lógica se deslice por todo el código base como kudzu.

    – zakdanzas

    22/10/2013 a las 23:06

  • I really wonder why the $injector isn't delaying construction of anything when it detects injection of anything that is a promise object Supongo que omitieron esta funcionalidad porque podría fomentar patrones de diseño que afectan negativamente la capacidad de respuesta de las aplicaciones. La aplicación ideal en su mente es una que sea realmente asíncrona, por lo que la resolución debería ser un caso límite.

    – zakdanzas

    22/10/2013 a las 23:09

1646759653 222 Retrasar el cambio de ruta de AngularJS hasta que se
F Lekschas

Trabajé con el código de Misko anterior y esto es lo que hice con él. Esta es una solución más actual ya que $defer ha sido cambiado a $timeout. Sustituyendo $timeout sin embargo, esperará el período de tiempo de espera (en el código de Misko, 1 segundo), luego devolverá los datos con la esperanza de que se resuelva a tiempo. De esta manera, vuelve lo antes posible.

function PhoneListCtrl($scope, phones) {
  $scope.phones = phones;
  $scope.orderProp = 'age';
}

PhoneListCtrl.resolve = {

  phones: function($q, Phone) {
    var deferred = $q.defer();

    Phone.query(function(phones) {
        deferred.resolve(phones);
    });

    return deferred.promise;
  }
}

¿Ha sido útil esta solución?

Esta web utiliza cookies propias y de terceros para su correcto funcionamiento y para fines analíticos y para mostrarte publicidad relacionada con sus preferencias en base a un perfil elaborado a partir de tus hábitos de navegación. Al hacer clic en el botón Aceptar, acepta el uso de estas tecnologías y el procesamiento de tus datos para estos propósitos. Configurar y más información
Privacidad