AngularJS

El enfoque adecuado

 

TADHack Madrid

 

Mayo 2015

whoami

Gonzalo Ruiz de Villa Suárez

CoFounder @adesis

Google Developer Expert in Angular

+GonzaloRuizdeVilla

@gruizdevilla

 

Enfocar:

Dirigir la atención o el interés hacia un asunto o problema desde unos supuestos previos, para tratar de resolverlo acertadamente.

¿Por qué AngularJS?

Porque queremos utilizar el enfoque adecuado para resolver cada problema.

 

Reduce la distracción, centrando tu atención en elementos concretos.

Elementos de enfoque

  • Testing unitario

  • Testing e2e

  • Inyección de dependencias

  • Automatización

  • Estructura del proyecto

  • Módulos y bower

  • Componentes de AngularJS
    • Plantillas
    • Controllers
    • Servicios
    • Filtros
    • Directivas
    • Interceptors/decorators

Testing unitario

  • AngularJS está diseñado pensando desde el primer instante en los tests unitarios.
  • La inyección de dependencias facilita el mocking.
  • (Con)céntrate solo en el elemento que tengas entre manos.
  • Bad smell detector:

 

Encontrando el enfoque adecuado:

La importancia del primer test.


  beforeEach(inject(function($filter) {
    items = ['a', 'b', 'c', 'd', 'e', 'f', 'g', 'h'];
    str = "tuvwxyz";
    number = 100.045;
    limitTo = $filter('limitTo');
  }));


  it('should return the first X items when X is positive', function() {
    expect(limitTo(items, 3)).toEqual(['a', 'b', 'c']);
    expect(limitTo(items, '3')).toEqual(['a', 'b', 'c']);
    expect(limitTo(str, 3)).toEqual("tuv");
    expect(limitTo(str, '3')).toEqual("tuv");
    expect(limitTo(number, 3)).toEqual("100");
    expect(limitTo(number, '3')).toEqual("100");
  });

Tests e2e


  it('should filter results', function() {

    element(by.model('user')).sendKeys('jacksparrow');

    element(by.css(':button')).click();

    expect(element.all(by.repeater('task in tasks')).count()).toEqual(10);

    element(by.model('filterText')).sendKeys('groceries');

    expect(element.all(by.repeater('task in tasks')).count()).toEqual(1);
  });

Abordan cuestiones distintas

Pero comprueban el mismo cuerpo

Inyección de dependencias

 

 “Pedid y se os dará.”

Inyección de dependencias 


  module.controller("MyController", function (myService){
    var vm = this;
    myService
        .doSomething()
        .then(function (value){
            vm.value = value;
        });
  });


  var FN_ARGS = /^function\s*[^\(]*\(\s*([^\)]*)\)/m;
  var FN_ARG_SPLIT = /,/;

  fn.toString()
    .match(FN_ARGS)
    .split(FN_ARG_SPLIT)

¿Queremos minificar?


  someModule.controller('MyController', ['greeter', function(myService) {
    // ...
  }]);

Notación de array

Anotación mediante propiedad $inject


  var MyController = function(myService) {
      // ...
  }
  MyController.$inject = ['myService'];
  someModule.controller('MyController', MyController);

And try to stay
DRY!

 

with ng-annotate


  
  angular.module('myModule')
         .controller('MyController', MyController);

  /*@ngInject*/
  function MyController(myService) {
      // ...
  }

Automatización

Porque lo repetitivo distrae y nos hace perder el foco.

Para gustos colores

Broccoli

gulp.js

Grunt

¿Qué puedes automatizar?

  • JSHint
  • Minificación de HTML, CSS y JS
  • Concatenación de ficheros
  • ng-annotate
  • livereload
  • ¡mock server!
  • tests, tests, tests
  • Jade
  • i18n
  • ES6 transpile, coffescript, ...
  • bump de versiones
  • ....

¿Qué MÁS puedes automatizar?

Aquello que repitas habitualmente.

* "Puff, estoy hasta arriba, ya lo haré cuando termine esta entrega"

No te obsesiones con automatizar todo al principio y después tampoco seas "vago"* para automatizar lo que te haga perder tiempo.

Convention

over

configuration

Reduce el ruido

y simplifica la automatización, refactorización, el rediseño, etc.

  • ¿Donde están los componentes?
    • Carpetas
    • Nombres de ficheros
  • ¿Cómo se llaman los componentes?
  • ¿Cómo se llaman los tests?
  • ¿Cómo se llaman los módulos?
  • ¿Cómo son los estados?
  • ¿Cómo son mis APIs?
  • ¿Cómo se llaman mis entidades?
  • ¿Cómo formateo el código?

Convención,

porque soy como Dory

Se me olvida configurar y se me olvida lo configurado...

 

...y lo prefiero así.

Mantén las cosas relacionadas juntas y ordenadas

  • Elementos específicos
    • Vistas, controladores y servicios
  • Elementos comunes
    • Servicios
    • Interceptors
    • Filtros
    • Directivas

Inspírate en el equipo de Angular

 

https://github.com/angular/angular-seed

y en las guías de estilo de 

https://github.com/johnpapa/angular-styleguide

 

Cada vez van a ser más "opinionated" sobre la estructura del código.

No tengas miedo de cambiar la convención.

 

Pero hazlo de golpe

Una convención bien seguida se cambia muy rápido.

La mezcla de convenciones no es una convención.

Si tu proyecto crece, trocealo

Gestiona las partes independientes de forma independiente.

 

Utiliza repositorios independientes para cada parte.

bower y .bowerrc

  • Tu proyecto web tendrá dependencia de distintos componentes.
  • Si esos componentes están en repos privados, apóyate en el shorthand-resolver
  • Utiliza 'semver' para gestionar y etiquetar las versiones.
  • En el proyecto principal, "bower install usuario/componente --save"
 
     angular.module('myApp', ['miModule1', 'miModule2', 'miModule3'])

Otra vez, minimiza la configuración

 

main-bower-files

 

https://github.com/ck86/main-bower-files

Todo lo anterior multiplica la potencia de

Dirección de las dependencias

HTML y Controladores

Separación de responsabilidades.


.signup(ng-controller="SignupCtrl as signup")
    form(name="signupForm" ng-show="signup.state = 'START'")
        h1 Sign up
        label User
            input(ng-model="signup.model.user", required)
        label Name
            input(ng-model="signup.model.name", required)
        button(
            ng-disabled="signupForm.$invalid", 
            ng-click="signup.createAccount()") Create account

    div(ng-show="signup.state == 'LOADING'") 
      Creating account

    div(ng-show="signup.state == 'CREATED'") 
      Account {{signup.model.user}} created! 

    angular
      .module('myModule')
      .controller('SignupCtrl', SignupCtrl);
    
    function SignupCtrl(signupService){
        var vm = this;
        vm.state = "START";
        vm.model = {};
        vm.createAccount = createAccount;
        
        function createAccount(){
            vm.state = "LOADING";
            signupService.signup(vm.model).then(function(){
                vm.state = "CREATED";
            });
        }
    });

Servicios

Encapsulando la lógica de negocio.


    angular
      .module('myServices)
      .service('signupService', signupService);
  
    function signupService($http){
        this.signup = signup;

        function signup(data) { 
            return $http
                     .post('/api/signup', data)
                     .then(response => response.data);
        };
    };

Directivas

  • El sitio donde encapsular la manipulación del DOM
  • Mediante las directivas, AngularJS se convierte en un framework de frameworks:  d3.js, famo.us, Raphaël, ...

    pie-chart(data="populations")

NgModelController

Al adaptarnos a la filosofía AngularJS, seguimos manteniendo el foco sobre cada elemento.


  angular
    .module('miDirectives')
    .directive('usernameAvailable', usernameAvailable);

  function usernameAvailable(signupService) {
    return {
      require : 'ngModel',
      link : function($scope, element, attrs, ngModel) {
        ngModel.$asyncValidators.usernameAvailable = signupService.usernameAvailable;
      };
    }
  }
    

    input(ng-model="signup.user", required, username-available)

Las directivas permiten definir el comportamiento de un nuevo elemento. Aumentamos la semántica.

Las directivas permiten decorar elementos y hacer composición de funcionalidad de forma transversal.

Filtros


    ul
        li(ng-repeat="lineas in pedido.lineas | reverse") 
            .description {{linea.description}}
            .ammount {{ pedido.ammount | ammount }}
            
    p Total ammount: {{ pedido.ammount | ammount }}

Centraliza la definición de transformaciones de salida habituales. Los filtros no modifican el modelo.


    angular
      .module('miFilters')
      .filter('ammount', ammount);

    function ammount() {
        return function(value) {
            return value.currencyCode + ' ' + value.quantity;
        })
    })

Crosscutting Concerns

Y el poder de la inyección de dependencias

  • ¿Quieres hacer log de las llamadas un servicio? Decóralo, interceptando las llamadas.
  • ¿Quieres cachear un servicio? Decóralo, interceptando las llamadas.
  • ¿Quieres enviar credenciales en peticiones Ajax? Interceptadores de http.
  • ¿Quieres reintentar peticiones Ajax? Interceptadores de http.
  • ¿Quieres reintentar peticiones Ajax pero después de refrescar la sesión tras un 401 unauthorized ? Interceptadores de http.

Cuando cada línea de código parece que te importa.

¡Gracias!

goo.gl/anp6o7

 

¿Preguntas?

Made with Slides.com