Ironic-Starter

ironic

En Irontec llevamos varios proyectos exitosos desarrollados íntegramente con Ionic Framework. Durante dichos desarrollos, hemos identificado varias carencias en los starter oficiales y como Ionic Framework es open source, hemos querido aportar nuestro granito de arena desarrollando y publicando nuestro propio starter.

El objetivo principal de este starter es integrar desde el principio varios módulos útiles para el desarrollo de una aplicación; tales como captar y gestionar errores de forma más visual y así tener más control de lo que está pasando realmente dentro de la aplicación.

Además, al estar desarrollado siguiendo buenas prácticas de desarrollo, incita al desarrollador a seguir las misma pautas y a desarrollar proyectos más mantenibles y escalables.

Instalación

Antes de comenzar la instalación de Ionic Framework, hay que comprobar que Node.js está instalado. Para ello, ejecutaremos el siguiente comando:

node -v

Si Node.js está instalado, nos devolverá la versión actual que tenemos instalado. En el caso de que no nos reconozca el comando, tendremos que instalarlo.

Una vez que lo tengamos instalado, ejecutaremos el siguiente comando para instalar Ionic Framework y Cordova.

npm install -g ionic cordova

Al finalizar la instalación crearemos un nuevo proyecto basado en el Ironic-Starter.

ionic start MyApp https://github.com/irontec/ironic-starter

Buenas prácticas

El desarrollo de este starter está basado en las guías de estilos de @John Papa y @Todd Motto y está pensado para que los desarrolladores sigan las misma pautas. A continuación explicaremos las que en todo desarrollo intentamos tener en cuenta, pero si algún curioso desea ver las guías de estilo al completo, se pueden consultar en los siguientes enlaces: guía de estilos de John Papa y guía de estilos de Todd Motto.

Una única responsabilidad

Cada archivo del proyecto tiene que tener una única responsabilidad, sin importar el número de líneas que pueda tener. Cada responsabilidad hay que encapsular y aislarlo de los demás; así conseguiremos código ravioli.

Aunque en un principio tengamos la tentación de unificar varias funcionalidades con la excusa de que son pocas líneas, es muy importante empezar a separar el código desde el principio. A medida de que el proyecto vaya creciendo, aumentarán las  líneas de código y si no seguimos esta buena práctica, nos encontraremos con código espagueti.

La diferencia entre un código espagueti y uno ravioli se ve al hacer cambios en el código. En el espaguetti esa parte del código puede estar “enredado” con otras funcionalidades y provocar mal funcionamientos o conflictos indeseados. Sin embargo, en un código ravioli al estar todo empaquetado individualmente, cada cambio afectaría únicamente a esa parte; sin provocar daños colaterales.

Por ello, evitaremos el siguiente código:

// app.js

angular
    .module('app', ['ui.router'])
    .controller('SomeController', SomeController)
    .factory('someFactory', someFactory);

    function SomeController() { }

    function someFactory() { }

Y lo organizaremos de este modo:

// app.js
angular
   .module('app', ['ui.router']);
// someController.js

angular
    .module('app')
    .controller('SomeController', SomeController);

    function SomeController() { }
// someFactory.js

angular
    .module('app')
    .factory('someFactory', someFactory);

    function someFactory() { }

IIFE (Immediately Invoked Function Expression)

Es una buena práctica empaquetar los componentes en funciones que se invocan inmediatamente. De ese modo, evitamos que las variables y funciones estén más tiempo de lo esperado en el global scope. Al mismo tiempo, evitaremos posibles colisiones entre variables.

Por lo tanto evitaremos este código:

angular
    .module('app')
    .factory('logger', logger);

    // la función logger es añadida como variable global
    function logger() { }

Y usaremos el siguiente código:

(function() {
    'use strict';

    angular
        .module('app')
        .factory('logger', logger);

        function logger() {}
})();

Evitar colisiones de nombres

Para evitar la colisión de nombres, utilizaremos una convención de nombres única con separadores para los sub-módulos. Además el uso de separadores ayuda a definir los módulos y sus jerarquías.

Por ejemplo, app puede ser nuestro módulo raíz y app.dashboard y app.users pueden ser módulos que dependen de él.

Módulos

Declararemos los módulos sin asignarlos y lo haremos utilizando la sintaxis de los setters.

Por lo tanto, evitaremos esto:

var app = angular.module('app', [
    'ngAnimate',
    'ui.router',
    'app.shared'
]);

Y utilizaremos lo siguiente:

angular
    .module('app', [
        'ngAnimate',
        'ui.router',
        'app.shared'
    ]);

Usaremos angular.module(‘app’, []) para declarar un módulo y especificar sus dependencias (sintaxis setter) y angular.module(‘app’) para recuperar un módulo (sintaxis getter).

Controller as vm

Utilizaremos la sintaxis controller as en vez del clásico controller con $scope y capturaremos el this en una variable llamada vm (ViewModel).

Este método nos ayuda a prevenir el uso excesivo del $scope. En algunos casos es mejor mover un método a un servicio o factoría en vez de utilizar dicha variable. El $scope se debe utilizar únicamente cuando es necesario; por ejemplo al llamar a eventos utilizando $emit, $broadcast o $on.

/* evitar */
function Customer() {

    this.name = 'Irontec';

    this.sendMessage = function() {};
}
/* recomendado */
function Customer() {

    var vm = this;

    vm.name = 'Irontec';

    vm.sendMessage = function() {};
}
<input ng-model="vm.name"/>

Asociaciones en la parte superior del controlador

Colocaremos las asociaciones en la parte superior del controlador; facilita la lectura y ayuda a identificar las variables asociadas a la vista.

/* evitar */
function Sessions() {

    var vm = this;
    vm.gotoSession = function() {
       /* ... */
    };

    vm.sessions = [];

    vm.title = 'Sessions';
/* recomendado */
function Sessions() {

    var vm = this;

    vm.sessions = [];
    vm.title = 'Sessions';

    vm.gotoSession = gotoSession;
    ////////////

    function gotoSession() {
       /* */
    }

Delegar la lógica dentro de los controladores a servicios o factorías

Es recomendable delegar la lógica de los controladores a servicios o factorías para reutilizar la lógica empleada. Además se puede aislar en un test unitario y elimina dependencias a la vez que esconde detalles de la implementación.

Utiliza snippets

Para facilitar la escritura del código basado en AngularJS, es recomendable utilizar snippets. Para utilizar snippets de Angular en Atom, basta con ejecutar el siguiente comando:

apm install angularjs-styleguide-snippets

Beneficios (Utilidades incluidas)

Ironic-Starter incorpora varias utilidades listas para ser utilizadas. La mayoría están basados en proyectos de John Papa, pero han sido adaptadas para que sean totalmente compatibles con Ionic Framework.

features-ironic

Las utilidades integradas son las siguientes:

Exception handler

Captura y gestiona las excepciones; es una herramienta muy útil para mostrar las excepciones en pantalla y en consola.

function testException() {

    throw { message: 'Exception test error' };

}

Logger

Es una utilidad para mostrar mensajes de varios tipos en pantalla y en la consola. Es posible mostrar mensajes de información, error, alerta y debug.

function testLoggerInfo() {

    logger.info('This is a INFO test', infoData, 'Test title');

}

function testLoggerError() {

    logger.error('This is a ERROR test', errorData, 'Test title');

}

function testLoggerWarning() {

    logger.warning('This is a WARNING test', warningData, 'Test title');

}

function testLoggerDebug() {

    logger.debug('This is a DEBUG test', debugData, 'Test Debug');

}

Además cuenta con la opción de deshabilitar los mensajes de debug desde el loggerProvider.

angular.module('app')
    .config(loggerConfig)

function loggerConfig(loggerProvider) {

    loggerProvider.setDebugEnabled( true );

}

Modal

Es una factoría para mostrar y ocultar ventanas modales. Muy útil para reutilizar partes del código que comparten todos los modales.

function testModal() {

    modal.show('src/modal/modalTest.html', 'Modal as vm')
    .then(function(result) {
        // result
    }, function(err) {
       // error
    });
}

En la carpeta modal se puede ver un ejemplo de uso.

Router

Es una utilidad para capturar y gestionar errores de routing. Por ejemplo, si una aplicación intenta ir a una estado no existente, esta utilidad capturará ese evento y disparará una excepción.

function testRouterHelper() {

    $state.go('fake-state')

}

Estructura del proyecto

.
|____css
|____img
|____index.html
|____lib
|____src
  |____app.js
  |____contact
  | |____contact.html
  | |____contact.js
  | |____contact.module.js
  | |____contact.routes.js
  |____core
  | |____config.js
  |____layout
  | |____tabs.html
  |____main
  | |____main.html
  | |____main.js
  | |____main.module.js
  | |____main.routes.js
  |____modal
  | |____modal.js
  | |____modalTest.html
  |____util
    |____constants.js
    |____exception
    | |____exception-handler.provider.js
    | |____exception.js
    | |____exception.module.js
    |____logger
    | |____logger.js
    | |____logger.module.js
    |____modal
    | |____modal.js
    | |____modal.module.js
    |____router
    | |____router.js
    | |____router.module.js
    |____util.module.js

App.js

El objetivo del app.js será solamente el de crear el módulo principal y especificar sus dependencias; no hará nada más. Los métodos run y config se ejecutarán desde el archivo core/config.js.

Core

Como se ha dicho en el punto anterior, esta carpeta contiene la configuración del módulo principal; lanza los métodos config y run de la aplicación. Con esos métodos configura las rutas mínimas, configura el módulo logger y inicializa los componentes principales de la aplicación.

Layout

Esta carpeta es para organizar los archivos que se encargan del layout general de la aplicación. En este caso, solamente existe un archivo; tabs.html, que es el encargado de mostrar las pestañas de navegación.

Modal (ejemplo)

Contiene los archivos de ejemplo para ver cómo se debe utilizar la utilidad modal. Para ello hay un controller y una view mínimas.

Carpeta por cada funcionalidad

Siguiendo las buenas prácticas anteriormente citadas, una buena forma de organizar el código es crear una carpeta por cada funcionalidad principal. En el caso de este Starter, se han definido dos funcionalidades; una pestaña principal para mostrar las utilidades y una pestaña para mostrar información de contacto. Por lo tanto, hay dos carpeta: Main y Contact.

Por cada funcionalidad es recomendable dividir la lógica en varios archivos.

  • Feature.module.js: su única finalidad es declarar el módulo.
  • Feature.routes.js: especifica los diferentes estados de dicha funcionalidad.
  • Feature.js: es el controlador principal de dicha funcionalidad.
  • Feature.html: es la vista principal de la funcionalidad.

Aparte de esos archivos, puede que se necesiten más. Por ejemplo si una funcionalidad depende de alguna constante, sería conveniente crear una archivo llamado Feature.constants.js y declarar ahí dicha constante.



¿Te gusta este post? Es solo un ejemplo de cómo podemos ayudar a tu empresa...
Sobre Mikel Eizagirre

Desarrollador front-end y de apps móviles.

Queremos tu opinión :)