angular para grandes aplicaciones (3)

Cómo combinar angular con require

Jake Baddeley

archivado en: JavaScript / 11 octubre, 2015 / taller:

En las primeras dos entradas sobre cómo trabajar con angular cuando se trata de aplicaciones muy grandes, con decenas de controladores, vimos que era fundamental implementar algún sistema de carga bajo demanda, en inglés, lazy load, que permita recuperar un controlador y una vista solo cuando se necesita realmente. Y una manera de conseguir esto es combinando la librería Require con los módulos ui-router y ui-router-extra.

Estas son las entradas:

En esta terminaremos de ver cómo armar todo el tinglado, aunque iré un poco por encima, pues voy volado y he subido un repo a git que, espero, basta por sí solo para terminar de entender cómo funciona.

Y sin más preámbulos, paso a explicar la jugada.

Paso 1 - definición de controladores

En lugar de definir el mapeado de rutas en el config, lo ponemos en un archivo aparte en formato json. En el ejemplo es el que he llamado route-controllers.js.

[

{

"name": "main",

"url": "/main",

"templateUrl": "views/main.html",

"controller": "controllers/main"

},

{

"name": "secondary",

"url": "/secondary",

"templateUrl": "views/secondary.html",

"controller": "controllers/secondary"

}

]

Paso 2 - la página principal

En la página principal, es decir, en index.html, cargamos el require indicando cuál será el punto de entrada (el launch de los archivos js) y ponemos un ui-view, que será donde se irán cargando las vistas.

<body ng-app="demoRequire">

<header>

Angular - Lazy Load con Require

</header>

<ul>

<li><a href="#main">main</a></li>

<li><a href="#secondary">secondary</a></li>

</ul>

<div class="container">

<!-- Aquí se incluye el contenido cargado con require -->

<div ui-view></div>

</div>

<footer>

</footer>

<!-- Definimos en el data-main un solo punto de entrada -->

<script src="bower_components/requirejs/require.js" data-main="scripts/main.js"></script>

</body>

Paso 3 - main.js

En main.js preparamos el tinglado del require tal y como vimos en la entrada anterior. Algo así por ejemplo:

/* Definimos las rutas que se corresponden a cada "alias" */

require.config({

waitSeconds: 0,

paths: {

'angular': '../bower_components/angular/angular',

'angular-animate': '../bower_components/angular-animate/angular-animate',

'angular-cookies': '../bower_components/angular-cookies/angular-cookies',

'angular-mocks': '../bower_components/angular-mocks/angular-mocks',

'angular-resource': '../bower_components/angular-resource/angular-resource',

'angular-route': '../bower_components/angular-route/angular-route',

'angular-sanitize': '../bower_components/angular-sanitize/angular-sanitize',

'angular-scenario': '../bower_components/angular-scenario/angular-scenario',

'angular-touch': '../bower_components/angular-touch/angular-touch',

'angular-bootstrap': '../bower_components/angular-bootstrap/ui-bootstrap-tpls',

'angular-http-loader': '../bower_components/angular-http-loader/app/package/js/angular-http-loader.min',

'angular-ui-router': '../bower_components/angular-ui-router/release/angular-ui-router.min',

'jquery': '../bower_components/jquery/dist/jquery.min',

'moment': '../bower_components/moment/min/moment.min',

'ui-router-extras': '../bower_components/ui-router-extras/release/ct-ui-router-extras.min',

'lodash': '../bower_components/lodash/lodash.min'

},

/* Definimos las dependencias donde se necesitan */

shim: {

'moment':{

exports:'moment'

},

'angular': {

exports: 'angular',

deps: ['moment']

},

'angular-route': ['angular'],

'angular-cookies': ['angular'],

'angular-sanitize': ['angular'],

'angular-resource': ['angular'],

'angular-animate': ['angular'],

'angular-touch': ['angular'],
'angular-bootstrap': ['angular'],

'angular-http-loader' :['angular'],

'angular-ui-router': ['angular'],

'ui-router-extras': ['angular'],

'angular-mocks': {

exports: 'angular.mock',

deps: ['angular']

}

},

priority: ['angular']

});

/* Para que se carguen las dependencias sin volverse loco, ralentizamos
el proceso de inicialización de la aplicación.

ver: https://docs.angularjs.org/guide/bootstrap */

window.name = 'NG_DEFER_BOOTSTRAP!';

/* "Requerimos" los archivos definidos antes por su alias */

require([

'angular',

'app',

'angular-route',

'angular-cookies',

'angular-sanitize',

'angular-resource',

'angular-animate',

'angular-touch',

'angular-bootstrap',

'angular-http-loader',

'angular-ui-router',

'jquery',

'moment',

'ui-router-extras',

'lodash'

], function(angular, app) {

/* Inicializamos manualmente la aplicación (vd. supra) */

angular.element(document).ready(function() {

angular.bootstrap(document, ['demoRequire']);

});

});

Paso 4 - app.js

Ahora convertimos la definición del módulo normal, donde se indican las fases config y run, en un módulo require. Además, preparamos un sistema de promesas que enlaza con la definición de controladores y estados que hicimos en el jasonako. Algo así:

/* Convertimos la estructura del módulo principal en un "módulo require" mediante el método define, el cual recibe tres parámetros

el nombre del modulo: opcional;

el array de dependencias

y una función que contiene el código del módulo.

*/

define([

/*dependencias (re-definimos las rutas no declaradas antes) */

'angular',

'services/factory',

'services/utils'

],

/* Función principal del módulo require, en el que inyectamos las dependencias anteriores */

function (angular, Factory, Utils) {

'use strict';

/* Cacheamos el módulo angular en una variable que retornaremos
al final */

var demoRequire = angular.module('demoRequire', [

/* dependencias angular */

'ngCookies',

'ngResource',

'ngSanitize',

'ngAnimate',

'ngTouch',

'ui.bootstrap',

'ng.httpLoader',

'ngRoute',

'ui.router',

'ct.ui.router.extras'

]).config( function($stateProvider, $futureStateProvider) {

/* En esta función armamos todo el sistema de promesas que
pasaremos al stateFactory */

/* Ver: http://christopherthielen.github.io/ui-router-extras/#/future

When a transition is requested to a state that doesn't exists, $futureStatesProvider checks if the missing state maps to a FutureState, or any possible decendant of a FutureState.

When it finds a placeholder that may map to the missing state, it pretends that the transition occurred successfully (according to the URL).

It then begins the lazy loading of the full UI-Router state definition.
When that promise resolves, it re-attempts the transition. */

function requireCtrlStateFactory($q, futureState) {

/* Hacemos la promesa */

var d = $q.defer();

require([futureState.controller], function (controller) {

var fullstate = {

controller: controller,

name: futureState.name,

url: futureState.url,

templateUrl: futureState.templateUrl

};

if (futureState.urlParams) {

fullstate.url+='?' + futureState.urlParams;

}

if (futureState['abstract']) {

fullstate['abstract'] = futureState['abstract'];

}

/* Resolvemos la promesa */

d.resolve(fullstate);

});

/* Y la devolvemos */

return d.promise;

}

/* Vamos cargando los estados futuros en tiempo de ejecución */

$futureStateProvider.stateFactory('requireCtrl', requireCtrlStateFactory);

var loadAndRegisterFutureStates = function ($http) {

/* Cargamos route-controller y por cada item definimos un estado futuro */

return $http.get('route-controllers.json').then(function (resp) {

angular.forEach(resp.data, function (fstate) {

fstate.type = 'requireCtrl';

$futureStateProvider.futureState(fstate);

});

});

};

$futureStateProvider.addResolve(loadAndRegisterFutureStates);

})

.run(function($timeout) {

});

return demoRequire;

});

Paso 5 - controladores como módulos require

Y ya el último paso es definir los controladores como módulos require. Algo tan sencillo como esto:

define(['angular'], function (angular) {

'use strict';

return function ($scope) {

console.log("Controlador Main");

} /* #función */

});

Bueno, visto así todo el codigako puede parecer más complicado de lo que es en realidad. Pero viendo todo el scaffolding en conjunto, que he comentado con detalle, creo que estará todo más claro.

En próximas entradas veremos otras maneras de preparar arquitecturas para grandes aplicaciones, pero de momento lo dejo aquí.

|| Tags: , , ,

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

  • estrellica valoración positiva
  • estrellica valoración positiva
  • estrellica valoración positiva
  • estrellica valoración positiva
  • estrellica valoración positiva
  • 5 sobre 5 (3 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!

Los comentarios están cerrados.