Tabla de Contenidos

7.4 Resolve

Ya hablamos en Resueltos antes de llamar al controlador de la importancia que tenían las promesas en el servicio de rutas. Ahora vamos a explicar esa importancia y volveremos a usar las promesas en paralelo con un objeto.

Lo que vamos a ver es una funcionalidad del método when de $routeProvider.

Al definir una ruta , en el método when, le pasamos un objeto con las propiedades templateUrl y controller. Este objeto puede tener más propiedades que no vamos a ver excepto la propiedad resolve. Esta propiedad la podemos ver similar al objeto que vimos en promesas en paralelo con un objeto.

El objeto resolve contiene en cada una de sus propiedades funciones que retornan promesas 1) que se resolverán antes de llamar al controlador y a las que se pueden acceder desde el controlador si se inyectan en él.

¿Cuál es la utilidad de ésto? Para mí son dos:

La solución a estos 2 problemas sería hacer las llamadas asíncronas antes de mostrar la página y no mostrar la página hasta que no se hayan obtenido los datos. Pues bien, éso es justo lo que hace resolve. No navegará hasta la ruta hasta que no estén disponibles todos los datos que necesitamos.

Veamos ahora un ejemplo de cómo se usa. Como es un poco complejo, vamos a ir haciéndolo paso a paso.

El servicio sumaAsincrona

Lo primero que vamos a hacer es volver a usar la función que creamos para las promesas de sumaAsincrona, pero vamos a transformarla en un servicio ya que ahora se usará en otro sitio además del controlador.

app.factory("sumaAsincrona",['$q','$timeout',function($q,$timeout) {
  
  return function (a,b) {
     var defered=$q.defer();
     var promise=defered.promise;
      
     $timeout(function() {
        try{
           var resultado=a+b;
           defered.resolve(resultado);
        } catch (e) {
           defered.reject(e);
        }   
     },3000); 
      
     return promise;
  } 
  
}]);

Creamos el servicio sumaAsincrona que es una función que acepta 2 parámetros. No deberíamos tener problemas para entender este código ya que en unidades anteriores vimos cómo crear servicios. En caso de dudas, puedes repasar el tema 3.8 factory

La ruta con el resolve

Ahora vamos a crear una ruta para que haga uso del servicio sumaAsincrona, el cual retorna una promesa.

  $routeProvider.when('/pagina1', {
    templateUrl: "pagina1.html",
    controller: "Pagina1Controller",
    resolve: {
      suma2y3:['sumaAsincrona',function(sumaAsincrona) {
        return sumaAsincrona(2,3);
      }]
    }
  });

Al definir así la ruta , no se navegará hasta la página hasta que no se haya resuelto la promesa.De esa forma al llamar al controlador ya tendremos el dato calculado.

Al principio queda un poco raro que sea un objeto resolve que tiene propiedades cuyos valores son funciones y que esas funciones retornan promesas.

El uso desde el controlador

Ahora en el controlador ya podemos inyectar un nuevo servicio llamado suma2y3 cuyo valor será el resultado de la promesa.

app.controller("Pagina1Controller", ["$scope","suma2y3",function($scope,suma2y3) {
   $scope.mensaje2y3="suma de 2 y 3 es " + suma2y3;
}]);

No vamos a ver el caso de que la promesa sea rechazada, ya que se complica un poco cómo tratarlo. Aunque tienes el siguiente blog donde lo explican: AngularJS - resolve $routeChangeError

Parámetros

Lo siguiente que vamos a ver es qué ocurre si necesitamos que la promesa use los datos de la ruta para hacer los cálculos. Podríamos pensar que es tan sencillo como usar $routeParams pero en el resolve no es posible usar $routeParams sino que tenemos que usar $route.current.params.

Veamos ahora un ejemplo en el que suponemos que los datos a sumar vienen como parámetros de la ruta.

  $routeProvider.when('/pagina1/:a/:b', {
    templateUrl: "pagina1.html",
    controller: "Pagina1Controller",
    resolve: {
      suma2y3:['sumaAsincrona','$route',function(sumaAsincrona,$route) {
        var a=parseInt($route.current.params.a);
        var b=parseInt($route.current.params.b);
        return sumaAsincrona(a,b);
      }]]
    }
  });

Ejemplo

En este ejemplo lo que hacemos es mostrar 2 páginas llamadas:

En ambos casos los valores para hacer las 2 sumas (4 números en total) se obtienen de la ruta. De esa forma se ve la diferencia al obtener los parámetros en el controlador 5) o en el resolve 6).

También en ambos casos se hacen 2 sumas para ver cómo se puede usar más de una propiedad en el resolve.

<!DOCTYPE html>
<html ng-app="app">

<head>
  <script src="//ajax.googleapis.com/ajax/libs/angularjs/1.2.19/angular.min.js"></script>
  <script src='//ajax.googleapis.com/ajax/libs/angularjs/1.2.19/angular-route.min.js'></script>
  <script src="//code.angularjs.org/1.2.19/i18n/angular-locale_es-es.js"></script>
  <script src="script.js"></script>
</head>

<body>
  <h1>Esto es el titulo y no cambia</h1>
  <ul>
    <li><a href="#/pagina1/2/3/7/9">Ir a la p&aacute;gina 1.</a>.Aqui se tardar&aacute; en mostrar la p&aacute;gina pero ya estar&aacute;n los datos calculados.</li>
    <li><a href="#/pagina2/2/3/7/9">Ir a la p&aacute;gina 2</a>.La p&aacute;gina se cargar&aacute; inmediatamente pero se tardar&aacute; en calcular los datos</li>
  </ul>
  <div ng-view></div>

  <h3>Esto es un pie y no cambia</h3>

</body>

</html>


var app = angular.module("app", ['ngRoute']);

app.factory("sumaAsincrona",['$q','$timeout',function($q,$timeout) {
  
  return function (a,b) {
     var defered=$q.defer();
     var promise=defered.promise;
      
     $timeout(function() {
        try{
           var resultado=a+b;
           defered.resolve(resultado);
        } catch (e) {
           defered.reject(e);
        }   
     },3000); 
      
     return promise;
  } 
  
}]);


app.config(['$routeProvider',function($routeProvider) {

  $routeProvider.when('/pagina1/:a/:b/:c/:d', {
    templateUrl: "pagina1.html",
    controller: "Pagina1Controller",
    resolve: {
      suma2y3:['sumaAsincrona','$route',function(sumaAsincrona,$route) {
        var a=parseInt($route.current.params.a);
        var b=parseInt($route.current.params.b);
        return sumaAsincrona(a,b);
      }],
      suma7y9:['sumaAsincrona','$route',function(sumaAsincrona,$route) {
        var c=parseInt($route.current.params.c);
        var d=parseInt($route.current.params.d);
        return sumaAsincrona(c,d);
      }]
    }
  });
    
  $routeProvider.when('/pagina2/:a/:b/:c/:d', {
    templateUrl: "pagina2.html",
    controller: "Pagina2Controller"
  });
    
  
  
  
  $routeProvider.otherwise({
        redirectTo: '/pagina1/nada'
  });   

}]);


app.controller("Pagina1Controller", ["$scope","suma2y3","suma7y9",function($scope,suma2y3,suma7y9) {
   $scope.mensaje2y3="suma de 2 y 3 es " + suma2y3;
   $scope.mensaje7y9="suma de 7 y 9 es " + suma7y9;
}]);




app.controller("Pagina2Controller", ["$scope","$routeParams","sumaAsincrona",function($scope,$routeParams,sumaAsincrona) {
  var a=parseInt($routeParams.a);
  var b=parseInt($routeParams.b);
  var c=parseInt($routeParams.c);
  var d=parseInt($routeParams.d);
        
  sumaAsincrona(a,b).then(function(resultado){
        $scope.mensaje2y3="suma de 2 y 3 es " + resultado;
  });
  
  sumaAsincrona(c,d).then(function(resultado){
     $scope.mensaje7y9="suma de 7 y 9 es " + resultado;
  });
  
}]);

<h1>Soy la pagina 1</h1> 
Al mostrar la página ya se ven los datos:
<br>
{{mensaje2y3}}
<br>
{{mensaje7y9}}

<h1>Soy la pagina 2</h1>
Estamos esperando a que se muestren los datos
<br>
.....
<br>
......
<br>
{{mensaje2y3}}
<br>
{{mensaje7y9}}

Referencias

1) No hace falta que retorne obligatoriamente promesas , también puede retornar valores directamente
2) Ya veremos más adelante qué es compilar una directiva
3) Ej. llamar a $http
4) o similar
5) mediante $routeParams
6) mediante $route.current.params