@gruizdevilla
@adesis
Meetup AngularJS Madrid
23 de junio de 2015
Y contestemos a preguntas como
¿Por qué? y ¿cómo?
/* evitar */
angular
.module('app', ['ngRoute'])
.controller('UnControlador', UnControlador)
.factory('unaFactoria', unaFactoria)
.config(appConfigurator);
function UnControlador() {
/*
...
*/
}
function someFactory() {
/*
...
*/
}
/*
...
*/
¿leemos un fichero para saber que hay dentro?
// app.module.js
angular
.module('app', ['ngRoute']);
// unControlador.js
angular
.module('app')
.controller('UnControlador', UnControlador);
function UnControlador() { }
// unaFactoria.js
angular
.module('app')
.factory('unaFactoria', unaFactoria);
function unaFactoria() { }
// app.config.js
angular
.module('app')
.config(appConfigurator);
function appConfigurator() { }
acrónimo de Immediately Executed Function Expressions
aka "funciones anónimas autoejecutables"
aunque ni tienen por que ser anónimas y menos autoejecutables siendo anónimas
http://www.aprendiendonodejs.com/2011/11/expresiones-de-funcion-invocadas.html
/* evitar */
// logger.js
angular
.module('app')
.factory('logger', logger);
// la función logger queda añadida como variable global
function logger() { }
// storage.js
angular
.module('app')
.factory('storage', storage);
// la función storage queda añadida como variable global
function storage() { }
¿cuanto ensuciamos con esto?
// logger.js
(function() {
'use strict';
angular
.module('app')
.factory('logger', logger);
function logger() { }
})();
// storage.js
(function() {
'use strict';
angular
.module('app')
.factory('storage', storage);
function storage() { }
})();
/* evitar */
var app = angular.module('app', [
'ngAnimate',
'ngRoute',
'app.shared',
'app.dashboard'
]);
/* recomendado */
angular
.module('app', [
'ngAnimate',
'ngRoute',
'app.shared',
'app.dashboard'
]);
si no es necesario, entonces molesta
¿no hay un único elemento declarado por fichero?
* a partir de ahora presuponemos el IIFE rodeando siempre el código
/* evitar */
var app = angular.module('app');
app.controller('SomeController', SomeController);
function SomeController() { }
/* recomendado */
angular
.module('app')
.controller('SomeController', SomeController);
function SomeController() { }
“El mundo era tan reciente, que muchas cosas carecían de nombre, y para mencionarlas había que señalarlas con el dedo.”
Gabriel García Márquez (Cien años de soledad).
/* evitar */
angular
.module('app')
.controller('Dashboard', function() { })
.factory('logger', function() { });
/* recomendado */
// dashboard.js
angular
.module('app')
.controller('Dashboard', Dashboard);
function Dashboard() { }
// logger.js
angular
.module('app')
.factory('logger', logger);
function logger() { }
<!-- evitar -->
<div ng-controller="Customer">
{{ name }}
</div>
<!-- recomendado -->
<div ng-controller="Customer as customer">
{{ customer.name }}
</div>
/* evitar */
function Customer($scope) {
$scope.name = {};
$scope.sendMessage = function() { };
}
/* recomendado - pero espera un poco más... */
function Customer() {
this.name = {};
this.sendMessage = function() { };
}
/* evitar */
function Customer() {
this.name = {};
this.sendMessage = function() { };
}
/* recomendado */
function Customer() {
var vm = this;
vm.name = {};
vm.sendMessage = function() { };
}
/* jshint validthis: true */
var vm = this;
al estandarizar la nomenclatura con 'vm'
function SomeController($scope, $log) {
var vm = this;
vm.title = 'Some Title';
$scope.$watch('vm.title', function(current, original) {
$log.info('vm.title was %s', original);
$log.info('vm.title is now %s', current);
});
}
<input ng-model="vm.title"/>
/* más típico que la muerte de un personaje de GoT */
angular
.module('app')
.controller('Sessions', Sessions);
function Sessions() {
var vm = this;
function x(){
/*
...
*/
}
vm.gotoSession = function() {
/*
...
*/
};
/* ... y sigue con más líneas... */
¿Qué tiene mi controlador?
function y(){
/*
...
*/
}
function z(){
/*
...
*/
y()
/*
...
*/
}
vm.refresh = function() {
/*
...
*/
};
/* ... y sigue con más líneas... */
function v(){
/*
...
*/
}
function w(){
/*
...
*/
v()
/*
...
*/
}
vm.search = function() { // <- en la línea 1029 encontré la función que interesa
/* ... */
};
vm.sessions = [];
vm.title = 'Sessions';
/* ... y sigue con más líneas... */
tan denostado, hoy le veremos utilidad
/* recomendado */
function Sessions() {
var vm = this;
vm.gotoSession = gotoSession;
vm.refresh = refresh;
vm.search = search;
vm.sessions = [];
vm.title = 'Sessions';
////////////
function gotoSession() {
/* */
}
function refresh() {
/* */
}
function search() {
/* */
}
/* recomendado */
function Sessions(dataservice) {
var vm = this;
vm.gotoSession = gotoSession;
vm.refresh = dataservice.refresh; // 1 liner can be OK
vm.search = search;
vm.sessions = [];
vm.title = 'Sessions';
Una responsabilidad por servicio
(que no hacer una única función)
function dataService() {
var someValue = '';
function save() {
/* */
};
function validate() {
/* */
};
return {
save: save,
someValue: someValue,
validate: validate
};
}
Formato tradicional de servicio...
function dataService() {
var someValue = '';
var service = {
save: save,
someValue: someValue,
validate: validate
};
return service;
////////////
function save() {
/* */
};
function validate() {
/* */
};
}
La misma estrategia que con los controladores
<div my-example max="77"></div>
angular
.module('app')
.directive('myExample', myExample);
function myExample() {
var directive = {
restrict: 'EA',
templateUrl: 'app/feature/example.directive.html',
scope: {
max: '='
},
controller: ExampleController,
controllerAs: 'vm',
bindToController: true
};
return directive;
}
function ExampleController() {
var vm = this;
vm.min = 3;
console.log('CTRL: vm.min = %s', vm.min);
console.log('CTRL: vm.max = %s', vm.max);
}
function Avengers(dataservice) {
var vm = this;
vm.avengers = [];
vm.title = 'Avengers';
dataservice.getAvengers().then(function(data) {
vm.avengers = data;
return vm.avengers;
});
}
function Avengers(dataservice) {
var vm = this;
vm.avengers = [];
vm.title = 'Avengers';
activate();
////////////
function activate() {
return dataservice.getAvengers().then(function(data) {
vm.avengers = data;
return vm.avengers;
});
}
}
Ciclo de vida de nuevo router
angular
.module('app')
.controller('Avengers', Avengers);
function Avengers(storageService, avengerService) {
var vm = this;
vm.heroSearch = '';
vm.storeHero = storeHero;
function storeHero() {
var hero = avengerService.find(vm.heroSearch);
storageService.save(hero.name, hero);
}
}
angular
.module('app')
.controller('Avengers', Avengers);
Avengers.$inject = ['storageService', 'avengerService'];
function Avengers(storageService, avengerService) {
var vm = this;
vm.heroSearch = '';
vm.storeHero = storeHero;
function storeHero() {
var hero = avengerService.find(vm.heroSearch);
storageService.save(hero.name, hero);
}
}
angular
.module('app')
.controller('Avengers', Avengers);
/* @ngInject */
function Avengers(storageService, avengerService) {
var vm = this;
vm.heroSearch = '';
vm.storeHero = storeHero;
function storeHero() {
var hero = avengerService.find(vm.heroSearch);
storageService.save(hero.name, hero);
}
}
minimizando con ng-annotate
/* @ngInject */
// controllers
avengers.controller.js
avengers.controller.spec.js
// services/factories
logger.service.js
logger.service.spec.js
// constants
constants.js
// module definition
avengers.module.js
// routes
avengers.routes.js
avengers.routes.spec.js
// configuration
avengers.config.js
// directives
avenger-profile.directive.js
avenger-profile.directive.spec.js
/**
* recommended
*/
avengers.controller.spec.js
logger.service.spec.js
avengers.routes.spec.js
avenger-profile.directive.spec.js
// avengers.controller.js
angular
.module
.controller('AvengersController', AvengersController);
function AvengersController() { }
// logger.service.js
angular
.module
.factory('logger', logger);
function logger() { }
// avenger-profile.directive.js
angular
.module
.directive('xxAvengerProfile', xxAvengerProfile);
// usage is <xx-avenger-profile> </xx-avenger-profile>
function xxAvengerProfile() { }
angular
.module('app', [
//modulos compartidos
'app.core',
'app.widgests',
//modulos verticales
'app.customers',
'app.dashboard',
'app.layout'
]);
angular
.module('app.core', [
//modulos de Angular
'ngAnimate',
'ngSanitize',
//modulos horizontales
'blocks.router',
'blocks.logger',
'blocks.exception',
//modulos de terceros
'ui.router',
'angular.moment'
]);
angular
.module('app.dashboard', [
'app.core',
'app.widgests'
]);
Evitamos algunos errores de forma temprana
y
garantizamos uniformidad en los proyectos
// constants.js
/* global toastr:false, moment:false */
(function() {
'use strict';
angular
.module('app.core')
.constant('toastr', toastr)
.constant('moment', moment);
})();
// Constants used only by the sales module
angular
.module('app.sales')
.constant('events', {
ORDER_CREATED: 'event_order_created',
INVENTORY_DEPLETED: 'event_inventory_depleted'
});
// customers.routes.js
angular
.module('app.customers')
.run(appRun);
/* @ngInject */
function appRun(routerHelper) {
routerHelper.configureStates(getStates());
}
function getStates() {
return [
{
state: 'customer',
config: {
abstract: true,
template: '<ui-view class="shuffle-animation"/>',
url: '/customer'
}
}
];
}
// routerHelperProvider.js
angular
.module('blocks.router')
.provider('routerHelper', routerHelperProvider);
/* @ngInject */
function routerHelperProvider(
$locationProvider,
$stateProvider,
$urlRouterProvider
) {
/* jshint validthis:true */
this.$get = RouterHelper;
$locationProvider.html5Mode(true);
RouterHelper.$inject = ['$state'];
/* @ngInject */
function RouterHelper($state) {
var hasOtherwise = false;
var service = {
configureStates: configureStates,
getStates: getStates
};
return service;
///////////////
function configureStates(states, otherwisePath) {
states.forEach(function(state) {
$stateProvider.state(state.state, state.config);
});
if (otherwisePath && !hasOtherwise) {
hasOtherwise = true;
$urlRouterProvider.otherwise(otherwisePath);
}
}
function getStates() { return $state.get(); }
}
}