angular para grandes aplicaciones (1)

Las grandes aplicaciones en angular necesitan un sistema de carga bajo demanda.

Jake Baddeley

archivado en: JavaScript / 20 marzo, 2015 / taller:

Angular, al menos en las versiones que se están manejando en el momento de escribir estas líneas, las previas a la 2.0, tiene un sistema de rutas muy rudimentario que, además, tiene un problema: necesita que esté todo cargado para funcionar, lo cual se convierte en algo muy grave cuando la aplicación es grande e incluye cientos de controladores, directivas, factorías y demás mondongos.

Entre otras soluciones, para remediarlo podemos combinar el módulo ui.router de Nate Abele, con el añadido UI-Router Extras de Chris Thielen y el require.js de James Burke o, en su defecto, el módulo ocLazyLoad de Olivier Combe. No es un remedio sencillo, pero sin duda vale la pena implementarlo cuando nos enfrentamos a aplicaciones monstruosas.

Vamos paso a paso.

1. Preparando el tinglado

Este módulo permite organizar un sistema de rutas bastante más sofisticado que el ng-routes que tiene angular (y de aquí en adelante aviso que me estoy refiriendo a las versiones previas a la 2.0).

Entre otras maneras, podemos instalarlo con bower:

$ bower install angular-ui-router:

o con el npm de node:

$ npm install angular-ui-router

Lo cargamos en el index (ya veremos cómo reorganizar la carga cuando hable de require).

<head>

<title></title>

<script src="bower_components/angular/angular.min.js"></script>

<script src="bower_components/angular/angular-ui-router.min.js"></script>

</head>

Y lo inyectamos en nuestro módulo principal.

app.js

angular.module('demoRouter', ['ui.router']);

Ahora, para seguir esta explicación, podemos crear tres controladores, cada uno en su propio archivo.

controllers/main.js

angular.module('demoRouter')

.controller('main', function($scope, $log) {

$log.log('soy main');

});

controllers/foo.js

angular.module('demoRouter')

.controller('foo', function($scope, $log) {

$log.log('soy foo');

});

controllers/bar.js

angular.module('demoRouter')

.controller('bar', function($scope, $log) {

$log.log('soy bar');

});

Cargamos todo en el index.html

<script src="js/app.js"></script>

<script src="js/controllers/main.js"></script>

<script src="js/controllers/foo.js"></script>

<script src="js/controllers/bar.js"></script>

Preparamos las vistas parciales, esto es, las que incrustaremos en el marco principal (index.html).

views/bar.html

<p>Soy bar</p>

views/foo.html

<p>Soy foo</p>

Y, ale op, empezamos.

2. ui.router

Este módulo, realmente bueno, funciona con lo que denominan «estados». En función del estado definido en cada momento, se carga una vista u otra en el tag de una directiva denominada ui-view. Es decir, donde queramos que se vayan cargando las subvistas o vistas parciales ponemos esta directiva:

<section ui-view></section>

Luego podemos mostrar ahí un template u otro de tres maneras:

  1. Usando la directiva ui-sref
  2. Con el método $state.go()
  3. Mediante la navegación normal

Así, por ejemplo, con la directiva ui-sref podríamos definir una barra de navegación de esta manera:

<a ui-sref="foo">foo</a>

<a ui-sref="bar">bar</a>

Al pulsar en cada uno de los enlaces, el enlace (status-ref) nos llevaría al estado foo o al estado bar, los cuales definimos en el config:

angular.module('demoRouter', ['ui.router'])

.config(function($stateProvider, $urlRouterProvider) {

/* Definimos un estado default */

$urlRouterProvider.otherwise("/main");

/* Preparamos el mapa de rutas / estados */

$stateProvider

.state('main', {

url: "/index",

templateUrl: "views/main.html"

})

.state('foo', {

url: "/foo",

templateUrl: "views/foo.html",

controller: "foo"

})

.state('bar', {

url: "/bar",

templateUrl: "views/bar.html",

controller: "bar"

});

});

Es decir, en el config mapeamos las rutas de nuestra aplicación indicando qué plantilla (templateUrl) y qué controlador (controller) deben activarse en cada estado. Al pulsar en el ui-sref cambiamos el estado y, con este, la plantilla html y el controlador que están activos. Se pueden definir tanto el html a inyectar en la vista, usando como clave template en lugar de templateUrl, como el controlador en el propio jasonako, pero es más limpio hacerlo de la manera que indico.

Con state.go() el proceso es muy similar, solo que definimos el cambio de estado con este método, y con este lo que debe cargarse en ui-view:

index.html

<a ng-click="setEstado('foo')">foo</a>

main.js

/* Observad que se inyecta $state en el controlador */

angular.module('demoRouter')

.controller('main', function($scope, $log, $state) {

$scope.setEstado = function(estado) {

$state.go('foo');

}

});

2.1. Resolve

Con resolve podemos pasar al controlador datos cargados antes de que se instancie, incluidas promesas y llamadas ajax. Los resolves son un mapa de objetos que deben devolver un par clave-valor. Algo así por ejemplo:

$stateProvider

.....

.state('promesas', {

resolve: {

bazinga: function() {

return { 'hagBasin': 'Oh la la!'};

},

llamadaRest: function($http) {

return $http({method: 'GET', url: http://restcountries.eu/rest/v1/name/American Samoa'})

.then (function (data) {

return data;

});

}

},

url: "/promesas",

template: "<div>{{hagaBasin}} <p>La capital de Samoa es {{capital}}</p></div>",

controller: function($scope, bazinga, llamadaRest) {

$scope.hagaBasin = bazinga.hagBasin;

$scope.capital = llamadaRest.data[0].capital;

}

});

Además, podemos enviar datos especificándolos en la propiedad data (se podría utilizar otra, pero se recomienda este nombre para prevenir conflictos).

app.js

...

.state('bar', {

url: "/bar",

data: {

soyUnDato: "blues",

soyOtroDato: "soul"

},

templateUrl: "views/bar.html",

controller: "bar"

})

...

bar.js

angular.module('demoRouter')

.controller('bar', function($scope, $log, $state) {

$log.log($state.current.data. soyUnDato); // blues

$log.log($state.current.data. soyOtroDato); // soul

});

2.2. Eventos

No es necesario utilizar los eventos para la carga bajo demanda de archivos, como veremos en la siguiente entrada, pero, ya que estoy desmenuzando las posibilidades de ui router, los explicaré aunque sea de pasada.

Los eventos relacionados con los estados se engarzan al $rootScope y nos permiten hacer cosas:

  • cuando se va a pasar a otro estado ($stateChangeStart),
  • cuando no se encuentra el otro estado ($stateNotFound)
  • cuando se ha completado la transición al otro estado ($stateChangeSuccess)
  • cuando se ha producido un error durante el cambio de estado ($stateChangeError).

Los parámetros varían de un evento a otro y a la documentación me remito para verlos en detalle, pero la sintaxis básica de todos estos eventos es más o menos así:

/* Recordad que hay que inyectar el rootScope en el controlador */

$rootScope.$on('$stateChangeStart',
function(event, toState, toParams, fromState, fromParams){

/* Con un preventDefault detenemos el cambio de estado, lo que nos permite hacer algo antes de cambiarlo  */

event.preventDefault();

});

Además, tenemos dos eventos relacionados con las vistas:

uno que se dispara antes de que el DOM se haya renderizado ($viewContentLoading)

otro una vez que se ha renderizado ($viewContentLoaded)

$rootScope.$on('$viewContentLoading', function(event, viewConfig){

$log.log('Va a empezar el render');

});

El módulo ui router tiene más posibilidades, como la carga de varias vistas de forma simultánea, la definición de subvistas a partir de una vista abstracta, o el paso y la recepción de parámetros con queries strings, pero tratarlas ahora sí que nos alejaría del tema que estoy explicando, la carga bajo demanda de archivos en angular, así que las dejaré para otro momento y pasaré a explicar algunas cosas de ui router extras, aunque será en la siguiente entrada, que esta es ya demasiado extensa.

|| Tags: , , ,

valoración de los lectores sobre angular para grandes aplicaciones (1)

  • estrellica valoración positiva
  • estrellica valoración positiva
  • estrellica valoración positiva
  • estrellica valoración positiva
  • estrellica valoración negativa
  • 4.1 sobre 5 (12 votos)

¿Te ha parecido útil o interesante esta entrada?
dormido, valoración 1 nadapensativo, valoración 2 un poco sonrisa, valoración 3 a medias guiño, valoración 4 bastante aplauso, valoración 5 mucho

Tú opinión es muy importante, gracias por compartirla!

5 respuestas a “angular para grandes aplicaciones (1)

  1. Hola, Gracias por compartir este artículo con la comunidad de desarrolladores de AngularJS.

    Actualmente estoy en un proyecto donde se pretende empezar a usar AngularJS para la realización de nuevos módulos en un ERP, así como también la migración de los módulos ya existentes.

    Me gustaría saber si existe alguna diferencia importante entre la carga bajo demanda utilizando requireJs y ocLazyLoad.

    Saludos desde Venezuela.

  2. marcos el dijo:

    Hola Andrés, gracias a ti.

    Depende de la cantidad de “extras” que vayas a añadir a la aplicación. Si vas a poner muchas cosas no angularescas, como la librería jQuery, underscore, plugins jQuery, etcétera, conviene usar require, ya que te permite manejar también esas dependencias.

  3. Hola.
    Yo tengo la estructura ya creada de ui-route que cargan en un index que tiene su controlador para el tratamiento de idiomas y dentre de este un donde cargo la chicha de la página.

    Querria saber como puedo poniendo una url en el navegador tal que http://dominio.es/resultado/1-34B(seria el id que quiero buscar) me muestre bien la pantalla.

    Ya que a mi me funciona si ya tengo cargado el controlador que contiene el ng-view. Pero si la pego en otro navegador no funciona.

    .state(‘detalle’, {
    url: “/resultado/:id”,
    templateUrl: ‘html/resultado/resultado.html’,
    controller: ‘resulstadoCtrl’,
    })

    Me gustaría sabes si se puede hacer o es imposible ya que no carga el controlador general.

    Muchas gracias por atender mi duda.

  4. pablo, no sé si he entendido lo que dices. En cualquier caso, te sugiero que revises la documentación de ui-route en lo relativo al envío de parámetros.

  5. Si, creo que me he liado un poco. En resumidas cuentas es que si la url que se pone en el state con el parametro es algo que se muestra en un ui-view. Puedo acceder a esa url directamente? O es imposible ya que hace falta cargar lo que esta fuera del ui-view que puede que tenga su propio controlador.

    Mi problema no es el envío de parametro, que sí me funciona. Lo que necesito es saber si puedo hacer lo que arriba te indico.

    Te agradezco mucho tu rápido respuesta y atención.