Tabla de Contenidos

15.1 Estructura de ficheros

Mucho se ha hablado sobre la estructura de los ficheros en una aplicación de AngularJS y se seguirá hablando de ello ya que no existe una estructura definitiva que se pueda aplicar a todos los proyectos sino que depende de las características del propio proyecto como puedan ser el tamaño del proyecto, el tiempo de desarrollo o los conocimientos del equipo de desarrollo. Por todo esto , lo que vamos a explicar aquí no debe tratarse como reglar fijas sino guías a aplicar que deben ser personalizadas para cada proyecto.

En muchos tutoriales se empieza diciendo que hay una carpeta llamada app donde está el código fuente del proyecto, en nuestro caso no vamos a tener dicha carpeta ya que al estar dirigido este curso a un desarrollo en la parte servidor en Java y usando NetBeans nuestra carpeta con las páginas HTML, JavaScript, etc. se llama web.

La carpeta web incluye las subcarpetas WEB-INF y META-INF pero no vamos aquí a hablar de ellas ya que únicamente son importantes en la parte servidora y ya se habló sobre ellas en la unida de Servidor.

Carpeta raíz

Inicialmente nuestro proyecto debe tener la siguiente estructura:

app.js
index.html
index.js
main.html
main.js

El contenido del fichero app.js sería parecido al siguiente:

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

//Añade aquí las constantes 

app.config(['$routeProvider', function ($routeProvider) {
    //En este bloque config solo se configuran las rutas

    $routeProvider.when('/', {
        templateUrl: 'main.html',
        controller: 'MainController'
    });

    $routeProvider.otherwise({
        redirectTo: '/'
    });
}])


app.config([function () {
    //Bloque config para configurar el resto de cosas que no son las rutas.
}])

app.run([function () {

}]);

Variaciones

En vez de tener un único fichero app.js para definir el módulo, los bloques config , los bloques run, etc. Se podría dividir en cada uno de eso tipos quedando de la siguiente forma:

app-module.js
app-constant.js  
app-route.js
app-config.js
app-run.js

Vemos que hemos separamos las rutas y los bloques config en dos ficheros aunque definir una ruta se hace en un bloque config. Se ha hecho para dar mas importancia a la rutas.

Otra variación sería una versión intermedia entre las 2 anteriores:

app.js
app-route.js

Es decir que hacemos un fichero aparte solo para las rutas y dejamos el resto en app.js. El sentido es esto es porque suele ser lo que mas frecuentemente se mira ya que es necesario para recordar las operaciones que hay.

Servicios, directivas y filtros

Vamos a añadir una carpeta para los servicio, directivas , filtros, etc que son comunes a toda la aplicación.

app.js
index.html
index.js
main.html
main.js
common/
  directives/
    directive1.js
    directive2.js
  filters/
    filter1.js
    filter2.js
  services/
    service1.js
    service2.js

Variaciones

Si tenemos pocas directivas, filtros o servicios se pueden evitar las subcarpetas y ponerlos todos directamente en la carpeta common quedadon de la siguiente forma:

common/
  directive1.js
  directive2.js
  filter1.js
  filter2.js
  service1.js
  service2.js
  

En este último caso puede ser interesante añadir un sufijo en cada fichero para indicar el tipo de artefacto:

common/
  directive1-directive.js
  directive2-directive.js
  filter1-filter.js
  filter2-filter.js
  service1-service.js
  service2-service.js

Por otro lado , la carpeta common puede acabar creciendo y tener gran cantidad de ficheros en cada subdirectorio, en ese caso también deberías separar este directorio por funcionalidades.

common/
  funcionalidad1/
    directives/
    filters/
    services/
  funcionalidad2/
    directives/
    filters/
    services/
    

Realmente no hace falta explicar todo ésto, un poco de sentido común nos ayudará a organizar la carpeta common.

Para acabar veamos un ejemplo real de la carpeta common de la aplicación de compañia de vuelos Virgin America, sacada de ngEurope 2014 | Responsive Takes Flight

Librerias

Ahora vamos a añadir una carpeta con las librerías CSS, JavaScript , etc. de terceros. Pueden ser tanto módulos de AngularJS como otro tipo de librerías web.

app.js
index.html
index.js
main.html
main.js
common/
  directives/
    directive1.js
    directive2.js
  filters/
    filter1.js
    filter2.js
  services/
    service1.js
    service2.js
lib/
  bootstrap/
    css/
      bootstrap.css
      bootstrap.min.css
    fonts/
      glyphicons-halflings-regular.eot
      glyphicons-halflings-regular.svg
      glyphicons-halflings-regular.ttf
      glyphicons-halflings-regular.woff
    js/
      bootstrap.js
      bootstrap.min.js
  angularjs/
    js/
      angular.js
      angular-route.js
      angular-locale_es-es.js

Es importante en bootstrap mantener la estructura de css , fonts y js ya que los CSS de bootstrap hacen referencia a una ruta relativa llamad ”../fonts” por lo que debemos seguir esa estructura.

Variaciones

La carpeta lib también suele llamarse vendor. Personalmente prefiero el nombre de lib ya que así se llama igual que la carpeta lib donde guardamos las librerías Java quedando de esa forma todo mas coherente.

vendor/
  bootstrap/
  angularjs/

Por otro lado puede darse el caso que debamos usar varias versiones de la misma librería en la misma aplicación. Algo que puede suceder si estamos migrando a una nueva versión a mitad de proyecto pero no queremos modificar todo el código a la vez. En ese caos podríamos crear la siguiente estructura:

lib/
  angularjs/
    v1.2/
      js/
        angular.js
        angular-route.js
        angular-locale_es-es.js 
    v1.3/
      js/
        angular.js
        angular-route.js
        angular-locale_es-es.js 

Añadimos una subcarpeta mas indicando el número de versión. De esa forma podemos tener a la vez 2 versiones de la misma librería.

Esto último lo podríamos complicar un poco mas con una carpeta default para la librería que la aplicación debe usar por defecto si no dice nada sobre la versión. Aunque ya creo que así quizás nos estemos pasando de sobreingeniería.

lib/
  angularjs/
    default/
      js/
        angular.js
        angular-route.js
        angular-locale_es-es.js 
    v1.2/
      js/
        angular.js
        angular-route.js
        angular-locale_es-es.js 
    v1.3/
      js/
        angular.js
        angular-route.js
        angular-locale_es-es.js

Puede parecerte extraño que haya que usar varias versiones de una librería si las librerías se cargan todas en el index.html lo que implica que no se pueden cargar 2 versiones a la vez.

Lo que ocurre es que para mejorar el rendimiento se divide una gran aplicación de AngularJS en 2 aplicaciones independientes, por ejemplo:

  • La aplicación de los Administradores
  • La aplicación de los Usuarios

Si ambos conjuntos de páginas son muy distintas puedes crear 2 aplicaciones de AngularJS independientes cada una con su index.html y en ese caso si que podrías tener 2 versiones distintas de bootstrap o de AngularJS.

Funcionalidades

Ahora ahora hemos visto carpeta para preparar cosas de la aplicación de forma genérica pero sin incluir ninguna funcionalidad directa de la aplicación. Lo siguiente es crear una carpeta por cada una de las funcionalidades que tiene la aplicación. Dicho un poco mal y espero que se me perdone la frase: ”Habría que hacer mas o menos una carpeta por cada tabla de la base de datos.” o quizás dicho un poco mejor : ”Habría que hacer mas o menos una carpeta por cada entidad del interfaz REST

app.js
index.html
index.js
main.html
main.js
common/
  directives/
    directive1.js
    directive2.js
  filters/
    filter1.js
    filter2.js
  services/
    service1.js
    service2.js
lib/
  bootstrap/
    css/
      bootstrap.css
      bootstrap.min.css
    fonts/
      glyphicons-halflings-regular.eot
      glyphicons-halflings-regular.svg
      glyphicons-halflings-regular.ttf
      glyphicons-halflings-regular.woff
    js/
      bootstrap.js
      bootstrap.min.js
  angularjs/
    js/
      angular.js
      angular-route.js
      angular-locale_es-es.js
seguromedico/
  seguromedico-list.html
  seguromedico-list.js
  seguromedico-detail.html
  seguromedico-detail.js
  seguromedico.js
usuario/
  usuario-list.html
  usuario-list.js
  usuario-detail.html
  usuario-detail.js
  usuario.js

Cada carpeta de funcionalidades , define sus propias rutas. En nuestro ejemplo sería en el fichero seguromedico.js

Hay otra carpeta llamada usuario ya que podemos poner tantas carpetas de funcionalidades como queramos en el proyecto.

Variaciones

Al igual que con el fichero app.js, el fichero seguromedico.js tambien podría ser dividido en los siguientes ficheros:

seguromedico-constant.js
seguromedico-route.js
seguromedico-config.js
seguromedico-run.js

O como en la segunda alternativa de la siguiente forma:

seguromedico.js
seguromedico-route.js

En la que le damos mas importancia a las rutas por lo que las dejamos en un fichero aparte.

Estructuras jerárquica

Podemos ver cada una de éstas carpetas de funcionalidades como una pequeña aplicación por lo tanto sería posible en cada carpeta añadir de forma recursiva las carpetas que ya hemos explicado.

seguromedico/
  seguromedico-list.html
  seguromedico-list.js
  seguromedico-detail.html
  seguromedico-detail.js
  seguromedico.js
  
  common/
    directives/
    filters/
    services/
    
  lib/
    heatmap/
      heatmap.js
  
  
  asistenciamedica/
    asistenciamedica-list.html
    asistenciamedica-list.js
    asistenciamedica-detail.html
    asistenciamedica-detail.js
    asistenciamedica.js    

Único módulo

Una cosa que puede chocar mucho en esta estructura de ficheros es que solo se cree un módulo y se use la variable global app con ese único módulo. En la gran mayoría de páginas que hablan de este tema explican que no hay que usar la variable global app y que hay que separar la aplicación en diversos módulos.

La forma que recomienda todo el mundo es la siguiente:

angular.module('sub.module1');
angular.module('sub.module2');

angular.module('app',['sub.module1','sub.module2']);


angular.module('sub.module1')​.controller("Prueba1Controller",[function() {
    
}]);
 	    ​
angular.module('sub.module2')​.controller("Prueba2Controller",[function() {
    
}]);

Ahora bien , la forma que yo recomiendo es la siguiente:

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

app​.controller("Prueba1Controller",[function() {
    
}]);
 	    ​
app.controller("Prueba2Controller",[function() {
    
}]);

¿Porque recomiendo tener un único módulo en vez de varios y usar la variable global app en vez de obtener el módulo cada vez que se necesita? Pues la verdad es que no he encontrado ninguna razón para usar varios módulos en una aplicación. Cosa distinta es si estás haciendo una librería que será usada por terceros, en ese caso es necesario crear tu propio módulo ya que no sabes si vas a tener la variable app ni sabes el nombre del módulo de la aplicación, pero en el código de la propia aplicación no le veo mucho sentido.

Un posible motivo para tener varios módulos en una aplicación podría ser mejorar el rendimiento pero Briant Ford , uno de los desarrolladores de AngularJS comenta en su post Building Huuuuuge Apps with AngularJS lo siguiente “From a performance perspective, it doesn't really matter how you organize your modules, so choose whatever method best suits your project.”. Es decir que no ayuda para nada al rendimiento separar la aplicación en varios módulos. Sin embargo por otro lado en la presentación que hicieron en la conferencia de ng-europe sobre Angular 2.0 explican que van a usar los módulos de ES6 , habrá que ver como afecta ésto al rendimiento para tener uno o varios módulos.

Un motivo que he leído en algún post es que al hacer los test unitarios es mejor que cada módulo tenga pocos artefactos pero no veo como puede influir en los test que el módulo tenga pocos o muchos artefactos.

Otro motivo para tener varios módulos es porque al crear el módulo principal puedo ver de que módulos depende y así me hago una idea de las funcionalidades de la aplicación pero viendo los directorios puedo averiguar también eso mismo y es aun mas sencillo.

Una posible ventaja es la posibilidad de quitar una funcionalidad de la aplicación simplemente quitando dicho su módulo correspondiente como dependencia del módulo principal y todo ello sin quitar el código fuente de la aplicación.Y mejor mejor aun que el interfaz de usuario muestre o no esa funcionalidad dependiendo de si está o no el módulo. Quizás sea útil para alguien pero no creo que ese sea el objetivo de los módulos.

Por último la experiencia nos dice a todos que no es bueno clases muy grandes, ni métodos muy grandes ni directorios muy grandes, etc. Así que siguiendo ese sentido común tampoco sería muy recomendable tener módulos muy grandes. Pero no parece que sea un motivo de peso.

Ahora , ¿que ventajas me ofrece tener un solo módulo y la variable app? Pues la simplicidad, siempre que quiero crear algún artefacto en AngularJS es tan sencillo como recurrir a la variable app que siempre estará disponible en la aplicación. También lo normal es que la carpeta de funcionalidades y el nombre del módulo tengan nombres similares. Si cambio el nombre de la carpeta debo recordar de cambiar el nombre el módulo en todos los ficheros JavaScript de esa carpeta. ¿Y ese trabajo para que quiero hacerlo si no sirve para nada tener varios módulos? Luego cuando creas un artefacto tienes otro punto donde equivocarte al escribir el nombre del módulo, con lo fácil que es escribir app. Todo estos son pequeños inconvenientes que no tendría problema en sufrir si tener varios módulo sirviera para algo pero como no sirve para nada , ¿para que complicarnos la existencia aunque sea solo un poco?

Sigue el principio KISS y crea un solo módulo en tu aplicación con la variable global app.

Referencias

1) O lo que necesite la aplicación