angular para grandes aplicaciones (2)

angular y require js

Jake Baddeley

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

En la entrada anterior dedicada a ver cómo desarrollar en angular grandes aplicaciones gracias a la carga bajo demanda, expliqué que debemos combinar dos módulos angularescos -ui routes y su extensión, ui routes extra- con require js o, en su defecto, con el módulo ocLazyLoad. Creo que es mejor hacerlo con require, ya que nos permite manejar otras dependencias del proyecto y es esta librería la que veremos en esta segunda entrada sobre el tema. Quien ya la conozca puede pasar a la segunda parte de esta entrada.

3. Require

Require.js es una librería de JavaScript que permite distribuir el código js en módulos independientes y esto es muy útil en webs o aplicaciones webs complejas.

A diferencia de PHP, donde contamos con las funciones include y require para cargar archivos dinámicamente o de la función __autoload cuando estamos trabajando con clases, en JavaScript se carga todo a cascoporro en el index. Si la web apenas tiene un par de plugins y la librería jQuery, se puede hacer así sin problemas, pero hay escenarios más complicados, sobre todo cuando algunas librerías o scripts dependen de otros. Por ejemplo, no es raro encontrarse con una web que necesite jQuery, dos o tres plugins que dependen de esta librería, jQueryUI, underscore, otra más para trabajar canvas, como la kinetik, un sistema de plantillas como handelbars y, claro está, todo nuestro código más unos cuantos objetos JSON que nos ha devuelto el servidor o la API de turno. En estos casos es conveniente que tengamos todo ordenado con Require js.

Require se basa en la especificación de AMD,  la cual indica cómo tratar el código como módulos que podemos cargar de forma asíncrona y definiendo dependencias. Esto es, que si un módulo depende de otro, este se cargue conjuntamente con el primero. Esto, además de permitirnos desarrollar el código de forma más ordenada, nos facilita incorporar unos archivos u otros en función de lo que necesitemos realmente. Por ejemplo, si el dispositivo cliente es un ordenador, podemos cargar jquery.ui, pero si es un tablet, jquery.mobile.

Está previsto incorporar un sistema de módulos en ecma script 6, pero hasta que terminen de ultimar los detalles y se actualicen los navegadores, el tema va para largo.

3.1. Preparativos

Para ver cómo funciona vamos a crear dos archivos que guardaremos en la carpeta js. El ejemplo será una biblioteca virtual.

En libros.js preparamos un objeto que devuelve el libro seleccionado, es decir, que lo muestra por consola xD:

var objetoLibros = {

muestraLibros: function(libro) {

console.log(libro);

}

}

Y en otro, por ejemplo ariadna.js, preparamos una función que llama a la anterior después de haber definido el libro. Algo así:

var libros = {

libro_01: "La Ilíada",

libro_02: "La Odisea"

}

objetoLibros.muestraLibros(libros.libro_01);

Ahora podemos ir a index.html y cargar los archivos siguiendo un orden inverso, es decir, primero el que se necesitará después.

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

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

De esta manera, por consola se mostrará "La Ilíada". Ahora bien, si intentáramos hacerlo al revés, es decir, cargar primero ariadna.js y luego libros.js, nos daría error — Cannot read property 'muestraLibros' of undefined—, ya que uno depende del otro.

Este ejemplo es muy sencillo y, salvo que uno esté programando tajao perdido, es fácil cargar los archivos en su orden correcto, respetando las dependencias de unos con otros, pero, como digo, en cuanto la página sea compleja  —de esas con tropecientas librerías y trillones de archivos— es muy fácil perderse en un laberinto de interdependencias. Así que vamos a hacer lo mismo de antes, pero de manera más sensata con require.js.

La librería tiene tres métodos fundamentales:

  • config, en el que definimos los parámetros de configuración
  • require, con el que cargamos los módulos (los archivos)
  • y define, con el que definimos los módulos.

Empezamos con el primero.

3.2. Carga de módulos

Nos bajamos la última versión estable a mano o con algún instalador y la enlazamos en el header de nuestro index.html, pero al tag script normal para enlazar archivos js le añadimos el atributo data-main, en el cual especificamos qué archivo de js es el que debe cargar junto a la librería, que en nuestro caso es main.js, ubicado en la carpeta js.

<script data-main="js/main" src="js/vendor/require.js"></script>

De esta manera tenemos una sola puerta de entrada: main.js, que es, insisto, el archivo que se cargará de forma automática con la librería require.

Ahora en main.js podemos preparar la configuración mediante el método config, en el que se indican las rutas y otros datos que veremos más adelante.

Pero antes recordemos que nuestro proyecto tiene más o menos esta estructura:

  • root (carpeta raíz)
  • index.hml
  • otras cosas que trae el bolierplate
  • js
    • libros.js
    • ariadna.js
    • main.js
    • vendor (o bower o similar)
      • jquery-1.10.2.min.js
      • require.js

Y ahora vamos ya con el config.

El primer parámetro que nos interesa definir es baseUrl, que será el prefijo que se antepondrá a cualquier ruta. Como todos se encuentran a partir de la carpeta js, podemos usar esta como baseURL. Y luego ya definimos un path, una ruta, por cada uno de los archivos que necesitemos cargar en main.js, que serán ariadna.js, libros.js, que se encuentran al mismo nivel que main.js, y la librería jQuery, que está por debajo, en la carpeta vendor. Esta definición de rutas la hacemos en paths. Algo así:

require.config({

baseUrl: 'js',

paths: {

ariadna: 'ariadna',

libros: 'libros',

jquery : 'vendor/jquery-1.10.2.min'

}

});

Observad que no se añade la terminación .js, pues require da por sentado que serán archivos de JavaScript salvo que se le especifique lo contrario.

El segundo método fundamental de esta librería es require, con el ya sí vamos cargando los archivos. Este método acepta dos parámetros. En el primero se define el nombre de lo que queremos cargar tal y como lo hemos escrito en el config y en el segundo, opcional, la función callback, el alias, con el que se usará en el resto del código.

require(["jquery", "ariadna", "libros"], function($, ariadna, libros) {

});

Y ahora ya solo faltaría refactorizar los dos archivos para convertirlos en módulos mediante el método define, en el que podemos indicar tres parámetros: el nombre del modulo, el array de dependencias y la función que lo wrapea:

libros.js

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

return objetoLibros = {

muestraLibros: function(libro) {

console.log(libro);

}

}

});

ariadna.js

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

var libros = {

libro_01: "La Ilíada",

libro_02: "La Odisea"

}

objetoLibros.muestraLibros(libros.libro_01);

});

Hay muchas maneras de definir módulos y dependencias, pero lo que me interesa ahora es que entendamos que en el config definimos las rutas y otros parámetros que veremos luego, que con require requerimos los módulos y que con define definimos los módulos.

4. Angular y require

Hay unas tropecientas mil maneras de combinar angular con require. Veamos una sencilla. En el index podemos tener algo parecido a esto:

<!doctype html>

<html ng-app="app">

<head>

<title>Bazinga :P</title>

</head>

<body ng-controller="mainCtrl">

{{titulo}}

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

</body>

</html>

Es decir, una aplicación (app), con un controlador (mainCtrl), y solo un archivo cargado, el require, en un tag script en el que hemos definido cuál será la puerta de entrada en el atributo data-main.

En el archivo js/main.js definimos el config y lo cargamos con require.

require.config({

paths: {

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

'app': 'app',

'mainCtrl': 'controllers/main'

},

shim: {

'angular': { exports: 'angular' }

},

priority: [

'angular'

]

});

require([ 'angular', 'app', 'mainCtrl'], function(angular, app) {

/* En ocasiones puede ser necesario arrancar la aplicación de forma
manual. No es el caso, pero os dejo esto de pista */
/* angular.element(document).ready(function() {
angular.bootstrap(document, ['app']);
}); */

});

Hasta aquí, no hay casi nada diferente respecto a lo que vimos antes, salvo que en los parámetros de configuración he incluido un shim. Esto nos sirve cuando el archivo que estamos incluyendo no está definido como un módulo (amd), como es el caso de angular, y nos permite definir sus dependencias y su nombre.

Ahora en el archivo app.js definimos el módulo de require que contendrá el módulo principal de la aplicación. Fijaos que en el primer parámetro definimos su nombre (app) y en el array de dependencias le pasamos angular, para que require sepa que debe tener cargado el frame cuando ejecute este código.

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

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

/* Aquí podemos hacer cualquier cosa habitual en la
definición del módulo, como cambiar cosas en el .config()
o en el .run() */

return app;

});

Y ya solo nos falta definir los controladores que necesitemos como módulos:

define(["app"], function(app) {

app.controller('mainCtrl', function($scope, $log) {

// Aquí la lógica de este controlador

$scope.titulo = "Bazinga!";

});

});

Bueno, pues hasta aquí por hoy. En la tercera y última entrada sobre este tema veremos cómo apañar todo este lío para que nos funcione para cargar archivos bajo demanda con ui router extras. Es un poco lío, pero, insisto, es la única manera de desarrollar grandes aplicaciones en angular y no morir en el intento.

|| Tags: , , ,

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

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