You are on page 1of 32

BUENAS PRÁCTICAS EN ANGULARJS Marino Posadas

1.X @marinoposadas

IMPORTANTE
Este documento forma parte de nuestro curso de la versión 1.X de Angular, cuyo
nombre oficial es AngularJS y que tiene previsto seguir actualizándose al menos un
par de años más desde la aparición de Angular.
En Septiembre de 2016 salió Angular 2, a partir de entonces su nombre oficial es
simplemente Angular (independientemente de la versión). Angular rompe la
compatibilidad hacia atrás con AngularJS, ya que se trata de un framework
totalmente diferente que ha sido reescrito desde cero.
Si vas a empezar un proyecto nuevo y no sabes por cual decidirte, te recomendamos
Angular. Si necesitas aprender este nuevo framework, también podemos ayudarte.

www.campusmvp.es

GUÍA DE BUENAS PRÁCTICAS
Existen algunas guías que los expertos recomiendan basadas en los miles de
proyectos que están actualmente en producción.
Dos de esas guías (las más valoradas), son las de John Papa y Todd Moto (ambos
son Google Developer Experts).
Lo que sigue es un compendio de los puntos más relevantes dentro de ellas,
diseñadas especialmente para las versiones 1.x de Angular.

www.campusmvp.es

A destacar: Principio de responsabilidad única Manipulación del DOM Ámbitos de JavaScript Proceso de minificación Módulos Guías de Nomenclatura Funciones con Nombre vs. Funciones Estructura de la aplicación anónimas Módulos y dependencias de módulos Controladores Servicios y Factories Resolución de Promises www.campusmvp. PUNTOS PRINCIPALES Estas buenas prácticas se basan en un conjunto de puntos que recogen situaciones reales del día a día del desarrollo con aplicaciones Angular.es .

También puede entenderse que la construcción de funciones pequeñas (o sea.campusmvp. y mejor que no supere las 400 líneas de código. evitando colisiones en el equipo de trabajo. PRINCIPIO DE RESPONSABILIDAD ÚNICA Un componente por archivo. www.  Puede evitar "bugs" ocultos que surgen a menudo cuando se combinan componentes en un archivo que puede compartir variables o dependencias inesperadas. < 75 líneas de código) es preferible por mantenimiento. reutilización y deployment.es .  Eso facilita los test unitarios y los "mocking"  Hace que sean más fáciles de leer y de mantener.

js angular.es .campusmvp. function SomeController() { } /* recomendado */ // some.controller.js angular.module('app').module('app').module. function someFactory() { } www.PRINCIPIO DE RESPONSABILIDAD ÚNICA /* recomendado */ // app. ['ngRoute']). someFactory). SomeController).module('app'.controller('SomeController'.factory. /* recomendado */ // some.factory('someFactory'.js angular.

js angular. factory('logger'. logger).ÁMBITOS DE JAVASCRIPT Envolver los componentes Angular en Immediately Invoked Function Expressions (IIFEs). // logger se añade como variable global // storage.es . storage).js angular. constantes y recursos similares. // storage se añade como variable global Esto contribuye a rellenar innecesariamente el entorno global.module('app').factory('storage'. /* Evitar */ // logger.module('app') .campusmvp. y colisión de definiciones. www. y aumenta la posibilidad de "memory leaks". Especialmente idóneo para "helpers".

function logger() { } })().es .js (function() { 'use strict'. angular.campusmvp. ÁMBITOS DE JAVASCRIPT En lugar de lo anterior. es preferible usar: /** recomendado * * no se dejan globales detrás*/ // logger.module('app'). factory('logger'. www. logger).

[ 'ngAnimate'. es difícil que se necesite usar variable intermedia en la declaración del módulo: /* recomendado */ angular. y tiene dos módulos dependientes de ella. La aplicación se llama "app".module('app'.clientes'.proveedores".campusmvp. MÓDULOS Para evitar colisiones de nombres. Con nomenclatura compuesta: se organizan claramente las dependencias de cada módulo. 'app.proveedores' ]).es . En las definiciones. www. "app. es recomendable utilizar una nomenclatura para el nombre de los módulos. 'app. si se mantiene la filosofía de un componente por archivo.clientes" y "app. 'ngRoute'.

module('app') . function SomeController() { } Los módulos se crean una sola vez.es . MÓDULOS Lo mismo puede decirse de la declaración de controladores. // para recuperar una referencia angular. nos evitamos el uso de una variable intermedia.campusmvp. www. Si concatenamos las definiciones de controladores.module('app'). []).controller('SomeController'. SomeController). /* recomendado */ angular.module('app'. y se recuperan para referencia con una llamada // para crear un módulo: angular.

campusmvp. logger).  El código es más legible y se reduce la cantidad de "código anidado"  En vez de: angular. controller('DashboardController'. controller('DashboardController'. function() { }). FUNCIONES CON NOMBRE VS.js angular . DashboardController). module('app').es .js angular. function DashboardController() { } // logger. function() { }). es preferible usar funciones con nombre.  Es preferible: /* recomendado */ // dashboard. function logger() { } www.factory('logger'.module('app'). factory('logger'. ANÓNIMAS Cuando se pasa un "callback".module('app').

} } www.es . vm.title = 'Avengers'. vm.avengers. return vm. activate().). vm.avengers = data.info('Activated Avengers View'). Usa declaraciones de funciones con nombre en lugar de "function expressions".then(function() { logger. variables extra. /* * recomendado * Usar declaración de funciones y los miembros bindable al principio.avengers = []. function activate() { return getAvengers().then(function(data) { vm. Así se evitan muchos efectos colaterales (orden de declaración. logger) { var vm = this. }). }).campusmvp. Puedes usar una llamada a función antes de declarar la función gracias al "hoisting".FUNCIONES CON NOMBRE PARA OCULTAR DETALLES DE IMPLEMENTACIÓN EN LOS CONTROLADORES Mantén los elementos a enlazar (bindable) al principio de las declaraciones.getAvengers(). } function getAvengers() { return avengersService. */ function AvengersController(avengersService.getAvengers = getAvengers. etc.

name }} </div> www.es . que es más contextual.  Ayuda a evitar el uso de llamadas $parent en vistas con controladores anidados. La sintaxis controllerAs es más cercana al constructor de JavaScript que la sintaxis clásica.evitar--> <div ng-controller="CustomerController"> {{ name }} </div> <!-.recomendado --> <div ng-controller="CustomerController as customer"> {{ customer.  Promueve el uso de "objetos de referencia" en la vista (por ejemplo customer. <!-. fácil de leer.CONTROLADORES Usar la sintaxis "controllerAs" en las vistas  Los controladores son regenerados y proporcionan una nueva única instancia.campusmvp. y evita cualquier problema de referencia que puedan producirse en ausencia de "punteado".name en lugar de name).

$scope. $broadcast o $on.campusmvp.sendMessage = function() { }. cuando hagamos publicación y/o suscripción a eventos utilizando $emit. } /* recomendado – (pero lee la siguiente sección) */ function CustomerController() { this. Por ejemplo. /* evitar. this.name = {}.  Considera el uso de $scope en un controlador sólo cuando sea necesario.CONTROLADORES Usar la sintaxis "controllerAs" en los controladores  Utiliza la sintaxis controllerAs en vez de la clásica con $scope.sendMessage = function() { }.  Ayuda a evitar la tentación de utilizar métodos $scope en el interior de un controlador cuando se puede evitar o moverlos a una factory. } www.*/ function CustomerController($scope) { $scope.  La sintaxis controllerAs utiliza this dentro de los controladores para referenciar $scope  controllerAs es azúcar sintáctico para referirse a $scope. y hacer referencia a ellos desde el controlador. Se puede usar igual en la vista y se accede de la misma forma a los métodos de $scope.es .name = {}.

es .campusmvp.sendMessage = function() { }.sendMessage = function() { }.CONTROLADORES Usar la sintaxis "controllerAs" en los controladores con vm  Esto es una variante de lo anterior en casos en que el contexto this pueda ser problemático.  Se recomienda "cachear" this en otra variable (como vm. vm. this. } /* recomendada */ function CustomerController() { var vm = this. vm.name = {}. /* evitar*/ function CustomerController() { this. } www.name = {}. en referencia a "ViewModel"). y usarla en lugar de this.

campusmvp.LLEVAR LA LÓGICA DE LOS CONTROLADORES A SERVICIOS La lógica puede ser reutilizada por múltiples controladores cuando se coloca dentro de un servicio y se expone a través de una función. La lógica de un servicio puede ser aislada más fácilmente en una prueba unitaria. Mantiene el controlador ligero.es . www. Elimina dependencias y oculta los detalles de implementación del controlador. Podemos comparar el código siguiente para ver las diferencias. "sin extras" y focalizado en su operativa. mientras que la lógica que llama en el controlador puede saltarse fácilmente.

checkCredit = checkCredit.total = 0. $q. }.campusmvp.isCreditOk = vm.es . userInfo) { var vm = this. function checkCredit() { var settings = {}. // Obtenet la URL de base del servicio de crédito de la configuración // Asignar las cabeceras necesarias para llamar al servicio // Preparar la query string (URL) o el objeto de datos para el request // Añadir credenciales del usuario para que el servicio lo identifique adecuadamente // Usar JSONP para el navegador si no soporta CORS return $http.LLEVAR LA LÓGICA DE LOS CONTROLADORES A SERVICIOS /* evitar */ function OrderController($http. } www. vm.total <= maxRemainingAmount }). vm.isCreditOk. catch(function(error) { // Interpretar error // // Lidiar con timeout? retry? Intentar servicio alternativo? // Volver a rechazar con el error adecuado }).get(settings).then(function(data) { // Desempaquetar datos JSON en el objeto Response // // para buscar maxRemainingAmount vm. config. vm.

si la lógica se encuentra en un servicio.isOrderTotalOk(vm. function checkCredit() { return creditService.campusmvp. vm. } www.checkCredit = checkCredit.then(function(isOk) { vm.LLEVAR LA LÓGICA DE LOS CONTROLADORES A SERVICIOS Sin embargo.catch(showError). }. El código resultante es más reutilizable y más sencillo de depurar.isCreditOk.es .isCreditOk = isOk. vm. la expresión es mucho más simple.total = 0.total) . vm. }) . /* recomendado */ function OrderController(creditService) { var vm = this.

Es mejor delegar la lógica a una factoría y mantener el controlador simple y centrado en su labor.es . www.MANTENER LA LÓGICA DE LOS CONTROLADORES FOCALIZADA Definir un controlador para una vista y tratar de no reutilizarlo para otras vistas.campusmvp. La reutilización en este caso es frágil y dificulta los test end-2-end.

logger).module('app'). } // factory angular.campusmvp. } www. // servicio angular. es preferible utilizar factories.service('logger'. FACTORIES Los servicios se instancian con new y se usa this para métodos y variables públicas. function logger() { this. por consistencia.es .module('app'). function logger() { return { logError: function(msg) { /* */ } }. logger).factory('logger'.SERVICIOS VS. Así que.logError = function(msg) { /* */ }. Como los servicios son singleton solo hay una instancia de un servicio dado por inyector.

almacenamiento local. o cualquier otra operación similar. Una vez que una fábrica comienza a exceder ese propósito particular.campusmvp.es . se debe de refactorizar la lógica de sus operaciones e interacción en una factoría. que estará encapsulada por su contexto. Las factories son únicas y retornan un objeto que contiene los miembros del servicio. Respecto a los servicios de datos (que devuelven datos).FACTORIES Y EL PRINCIPIO SINGLE RESPONSIBILITY Una factory debe tener una única responsabilidad.  Además.  La responsabilidad de los controladores es la de recuperar información para la vista (nada más). se debe crear una nueva fábrica. la implementación del acceso al servicio puede ser muy específica de un repositorio y no necesita estar expuesta a otras acciones. Hay que hacer que los servicios sean los responsables de las llamadas XHR (AJAX). www.  Facilita los test de todas clases. o en memoria.

function getAvengers() { return $http.FACTORIES Y EL PRINCIPIO SINGLE RESPONSIBILITY El código siguiente ilustra la recomendación oficial (oculta implementaciones): /* recomendado */ // dataservice factory angular.es . dataservice). } function getAvengersFailed(error) { logger.data). function dataservice($http. dataservice. logger) { return { getAvengers: getAvengers }.error('XHR Failed for getAvengers.then(getAvengersComplete) . } } } www.campusmvp.$inject = ['$http'.data. function getAvengersComplete(response) { return response.module('app.results.core') .catch(getAvengersFailed).factory('dataservice'.' + error.get('/api/funcionDeDatos') . 'logger'].

devolver también una promesa en la función que llama.avengers.avengers = data.getAvengers() .es . ¿Por qué ?: Se pueden encadenar las promesas juntas y tomar medidas adicionales después de la llamada de datos en función de que se resuelva o rechace la promesa.LAS LLAMADAS A DATOS. function activate() { /** * Paso 1 * Llama a getAvengers para los datos y espera la promise */ return getAvengers(). }).info('Vista de Avengers Disponible').campusmvp. } www.then(function() { /** * Paso 4 * Ejecuta una acción al resolver la promise final */ logger. return vm. DEBEN DEVOLVER UNA PROMISE Al llamar a un servicio de datos que devuelve una promesa (como $http). }). } function getAvengers() { /** * Paso 2 * Pide los datos al servicio y espera la promise */ return dataservice. /* recomendado */ activate().then(function(data) { /** * Paso 3 * Asigna los datos y resuelve la promise */ vm.

MANIPULACIÓN DEL DOM Al manipular el DOM directamente.es . las plantillas de angular. si la directiva simplemente esconde y muestra el DOM. utiliza una directiva. CSS. Si hay formas alternativas. www. animaciones. ngShow o ngHide.campusmvp. depurar. y con frecuencia hay mejores formas (por ejemplo. Por ejemplo.  Las opciones disponibles dentro del módulo ngAnimate facilitan mucho esas tareas. plantillas). como el uso de CSS para definir estilos o los servicios de animación. mejor usar estas opciones. Razones:  La manipulación DOM puede ser difícil de probar. utilizar ngHide / ngShow.

controller('DashboardController'. Evitar el uso de la sintaxis de acceso directo a las dependencias que se declaran sin necesidad de utilizar un enfoque de minimización de fallos.es . Por ejemplo.) se convertirán a las variables mudas.controller('DashboardController'. function DashboardController(common. d). ¿Por qué ?: Los parámetros de los componentes (por ejemplo.no es seguro en minification*/ angular. function d(a. dataservice) { } Se pueden convertir en: angular. etc. factory. b) { } www.CUIDADO CON LOS PROCESOS DE MINIFYING EN D.campusmvp.module('app') . controller.I.module('app'). si tenemos: /* evitar. DashboardController).

'$routeParams'. DashboardController). $routeParams. function Dashboard($location.es .module('app') . 'dataservice']. function DashboardController($location.controller('DashboardController'. $routeParams. '$routeParams'.$inject = ['$location'. Dashboard]). 'dataservice'.controller('DashboardController'. common. ['$location'.campusmvp. DashboardController. 'common'. dataservice) { } www.I. dataservice) { } /* recomendado */ angular. Evitar crear largas listas de dependencias "in-line".module('app') . 'common'. common. y utilizar $injector en su lugar: /* evitar*/ angular.CUIDADO CON LOS PROCESOS DE MINIFYING EN D.

campusmvp.tipo. Un patrón recomendado es característica.NOMENCLATURA Utilizar nombres coherentes para todos los componentes siguiendo un patrón que describa la función del componente y (opcionalmente) su tipo. www.js. La consistencia en el proyecto es de vital importancia. Hay 2 nombres para la mayoría de los activos:  El nombre del archivo (avengers. Las convenciones de nomenclatura deben simplemente ayudar a encontrar el código más rápido y hacer que sea más fácil de entender.es . así como la coherencia con el equipo de desarrollo. Si se expande a toda la empresa proporciona una enorme eficacia.controller.js)  El nombre del componente registrado en angular (AvengersController) Las convenciones de nomenclatura ayudan a proporcionar una forma consistente para encontrar el contenido de un vistazo.

js // rutas avengers.js // constantes constants.js // servicios/factories avenger-profile.js avengers. si disponemos de controladores y servicios en archivos distintos: /** * recomendado */ // configuracion avengers.js www.js logger.service.module.campusmvp.directive.js // directivas avenger-profile.controller.routes.controller.js // definición de módulos avengers.spec.es .config.js // controladores avengers.spec.spec.routes.js logger.service.directive.js avengers.NOMENCLATURA: NOMBRES DE ARCHIVOS Por ejemplo.spec.

es modular.ESTRUCTURA DE LA APLICACIÓN: LIFT Estructurar la aplicación de tal manera que se pueda localizar el código rápidamente.campusmvp. LIFT proporciona una estructura coherente que funciona bien. La estructura debe seguir las 4 pautas básicas LIFT. y hace que sea más fácil aumentar la eficiencia desarrollador mediante la búsqueda de código rápidamente.  Tratar de mantener un contexto DRY (Do not Repeat Yourself) www.es . e identificar el código de un solo vistazo. Otra forma de comprobar la estructura de su aplicación es preguntarse: ¿Con qué rapidez se puede abrir y trabajar en todos los archivos relacionados con una característica?  Localización fácil de nuestro código  Identificar el código de un vistazo  (F) Estructura plana (Flat) tanto como sea posible.

El módulo raíz dependerá del resto de módulos de dominio. Si hay un bloque reutilizable. eso debería ser un módulo.es . La funcionalidad específica se delega a cada módulo.ESTRUCTURA DE LA APLICACIÓN: MODULARIDAD Usar muchos módulos pequeños. www. y de los compartidos o reutilizables.  Encapsulación de responsabilidad Crear un módulo aplicación-raíz (root-module). auto-contenidos.  Ese será su único papel.  Su papel será aglutinar a todo el resto de módulos y características de la aplicación. Crear módulos por áreas (dominios) de aplicación.campusmvp.

com/toddmotto/angular-styleguide www.campusmvp.es .angularjs.REFERENCIAS Documentación oficial de AngularJS: https://docs.org/api Guía de John Papa: https://github.md Guías de Todd Moto: https://github.com/johnpapa/angular-styleguide/blob/master/a1/README.

X. Si lo que quieres aprender es Angular (versión 2 y sucesivas) te recomendamos este otro curso. Si necesitas dominar el desarrollo con AngularJS 1.NADIE NACE SABIENDO. a tu ritmo y podrás resolver cualquier duda que te surja. Aprenderás online. no lo dudes.X en campusMVP. ¡Nos vemos dentro del aula virtual! . ¿QUIERES APRENDER MÁS? Este documento forma parte de nuestro curso online de AngularJS 1.