AngularJS

Superheroic JavaScript MVW Framework

Javascript

Javascript

Todo es un objeto

var string = "texto",

    array = [1,2,3],

    objeto = {"a":1};

    funcion = function () {}

Ámbito funciones

var objeto = {
    propiedad: 1,
    metodo: function () {
        return this.propiedad;
    }
}

objeto.metodo() -> 1

Ámbito funciones

var funcion = objeto.metodo;

funcion(); -> ?!

// ES5 bindings
funcion.bind(objeto);

funcion.call(objeto);

Clase Java

class Coche {
    int puertas;

    static String[] tipos = ["grande", pequeño];

    Coche (puertas) {
        this.puertas = puertas;
    }
    int numPuertas () {
        return this.puertas;
    }
}

Coche coche = new Coche(3);

Clase JavaScript

function Coche(puertas) {
    this.puertas = puertas;
}

Coche.tipos = ["grande", "pequeño"];

Coche.prototype = {
    numPuertas: function () {
        return this.puertas;
    }
}

var coche = new Coche(3);

Closures

Functions which take a small amount of metadata that describes the environment in which they were defined.


var counter = (function () {
    var count = 0;
    return function () {
        console.log(count++);
    }
})();

counter(); // 0
counter(); // 1

Java 7 doesn't support closures

Prototypal inheritance

var object;
object = {
    "a": 1,
    "b": true,
    "c": [1,2,3]
};

object.__proto__ = {
    "d": 1,
    "e": true,
    "f": [1,2,3]
};

object.__proto__.__proto__ === {}.__proto__;

object.__proto__.__proto__.__proto__ === null;
var b = {
    "d": 1,
    "e": true,
    "f": [1,2,3]
};

var object = Object.create(b);

object["a"] = 1;
object["b"] = true;
object["c"] = [1, 2, 3];
function A() {
    this["a"] = 1;
    this["b"] = true;
    this["c"] = [1,2,3];
}

function B() {
    this["d"] = 1;
    this["e"] = true;
    this["f"] = [1,2,3];
}

A.prototype = new B();

var object = new A();

¿Qué es AngularJS?

  • Framework javascript para desarrollar páginas web dinámicas.

  • HTML como lenguaje de templating.

  • Sistema de componentes extendiendo la sintaxis HTML.

  • El data binding y la inyección de dependencias simplifica la programación.

  • Versión minificada y gzipeada ocupa 30KBs.

¿Qué es una página web dinámica?

Gmail

Google Maps

Facebook

AJAX

Asynchronous JavaScript and XML

jQuery

Usada en el 80% de las 10.000 páginas web más visitadas.

  • Gestión de eventos
  • Manipulación DOM
  • Animaciones
  • AJAX
  • Plugins

Gestión de eventos

  • Añadir un

      $(".selectorcss").click(function (ev) {
          alert("");
      });
    
  •   $( "body" ).on( "click", "script", function() {
          alert( $(this).text() );
      });
    

manipulación DOM

  • Leer el contenido de un input

      var contenido = $("input").val();
    
  • Asignar un valor a un input

      $("input").val("valor");
    
  • Añadir una clase css

      $("selector").addClass("clickado");
    
  • Crear un nuevo elemento y añadirlo a otro

      $(this).append(
          $("<h4>has hecho Click!</h4")
      );
    

AJAX

$.ajax( "ruta.php" )
  .done(function() {
    alert( "success" );
  })
  .fail(function() {
    alert( "error" );
  })
  .always(function() {
    alert( "complete" );
  });

Animaciones

$(".selectorcss").click(function (ev) {
  $( "#block" ).animate({
      opacity: 0.6
  }, 1500 );
});

Plugins

$('#example').popover({
    title: "titulo",
    placement: 'left',
    html: "<h4>Contenido</h4>"
});

Ejemplo

  • Crearemos una lista de tareas con jQuery.

  • El proceso nos ayudará a comprender los porqué del diseño de un framework MVC como AngularJS.

  • Creamos una lista para guardar las tareas

      <ol id="lista">
         <li>Elemento 1</li>
         <li>Elemento 2</li>
      </ol>
      <input placeholder="Escribe un elemento" />
      <button>Añadir</button>
    
  • Añadimos un gestor de eventos que inserta un nuevo elemento.

      $("button").click(function () {
          // Obtenemos el valor del cuadro de texto
          var contenido = $("input").value();
    
          // Creamos un nuevo elemento
          var nuevoElemento = $("<li></li>");
    
          // Añadimos el texto a dicho elemento
          nuevoElemento.text("contenido");
    
          // Añadimos el elemento a la lista
          $("ol").append(nuevoElemento);
      });
    
  • Añadimos la posibilidad de borrar elementos.

      <ol id="lista">
         <li>Elemento 1 <button>borrar</button></li>
         <li>Elemento 2 <button>borrar</button></li>
      </ol>
    
      $("ol").on("click", "button", function (ev) {
          $(this).parent().remove();
      });
    
  • Inicializamos la lista desde el servidor

      // Pedimos el contenido al servidor
      $.get( "lista.php" ).done(function(obj) {
    
          // Por cada elemento
          obj.forEach(function(value, index, collection) {
              $("ol").append($("<li></li>").text(value));
              $("ol [tooltip]").tooltip();
          });
      });
    
  • Añadamos un efecto, un tooltip por ejemplo

    Necesitaremos inicializar el plugin cada vez que añadamos un nuevo elemento

      $("button").click(function () {
          ...
          $("ol [tooltip]").tooltip();
      });
    
  • Añadamos soporte para salvar la lista

    ¿Dónde almacenamos la lista? Por ahora sólo tenemos HTML.

    ¿Sacamos del HTML, o matenemos paralelamente una lista?

  • ¿Y si deseamos usar la lista en varios lugares?

    • Deberemos tener cuidado con ámbito global.
    • El paso de referencias es complejo.

      var lista = [];
      
      $.get("lista").done(function(obj) {
        lista = obj;
      });
      
      $("ol").on("click", "button", function (ev) {
        lista.splice($index, 1);
      });
      
  • Más preguntas

    • ¿Es eficiente? ¿Cúal es coste de los selectores de jQuery?

    • ¿Y si recibimos eventos a traves de un bus de mensajes con un websocket?

    • ¿Y si queremos que la lista se visualize en varios lugares?

    • ¿Se os ocurre como definir unos tests para comprobar el funcionamiento de esta página?

Separation of concerns

http://en.wikipedia.org/wiki/Separation_of_concerns

  • Principio de diseño de software

    • dividir la responsabilidad en componentes diferentes
  • Sólo con jQuery carecemos de mucha infraestructura.

    • Componentes
    • Templates
    • Data binding
  • Un framework debe enfocarnos hacia buenas prácticas.

Angular y jQuery

  • jQuery sigue siendo fantástica

    • AngularJS incluye una versión reducida llamada jqLite.
  • AngularJS añade funcionalidad extra a jQuery

    • debe incluirse antes que angularjs.
  • angular.min.js (con jqLite incluido) 104K

  • jquery.min.js 84K

Tareas versión jQuery

Objetivos

- Gestión de eventos
- Manipulación DOM
- AJAX

Enlace

Tareas versión Angular

Objetivos

- introducción al patrón MVW
- data binding empleando interpolación y con ng-model
- los ámbitos creados por las directivas ng-controller y ng-repeater

Enlace

Data binding

Sincronización automática entre el modelo y la vista.

<body ng-app ng-init="name = 'World'">
    Say hello to: <input type="text" ng-model="name">
    <h1>Hello, {{name}}!</h1>
</body>

ejemplo

  • Usar sólo templates conlleva habitualmente:
    • Redibujar todo el componente
    • Gestionar los eventos para actualizar los datos.

  • Interpolaciones {{variable}}

    • Se reemplaza por su contenido
  • ng-model

    • Asocia un componente a una variable

Enlace

MVC: Model View Controller

MVW: Model View Whatever

  • modelos

    • POJO (Plain Old Javascript Objects)
  • ámbito

    • $scope conecta el dominio del modelo con la vista
  • controller

    • función que inicializa el objeto $scope
  • Vista

    • HTML extendido con interpolaciones y directivas

var scope = angular.element($("body")).scope();

El scope permite enviar mensajes hacía arriba o hacia abajo

Enlace

Lista de la compra

Objetivos

- eventos
- validación de formularios
- inyección de dependencias
- filtros

Enlace

Inyección de dependencias

  • Patrón de diseño orientado a objetos

  • Delegamos la construcción/obtención de objetos a un componente el inyector.

function Cafetera() {
    var molinillo = new Molinillo();
    var bomba = Bomba.getInstance();
    var calentador = app.get('Calentador');
    this.infusion = function() {
        molinillo.moler();
        calentador.on();
        bomba.bombear();
    };
}

El paso de referencias de objetos resulta compleja

function CoffeeMaker(molinillo, calentador, bomba) {
    this.infusion = function() {
        molinillo.moler();
        calentador.on();
        bomba.bombear();
    };
}

El inyector observará el nombre de los parámetros

https://github.com/angular/di.js

  • Un valor

    app.value('existencia', 42);
    
  • Una servicio

      app.service('lista', function () {
          var lista = [];
          return {
              get: function (id) {
                  return lista[id];
              }
          }
      });
    
  • Un servicio

      app.factory('services', function () {
        return $resource('/services/:serviceId', {serviceId:'@id'});
      });
    
  • inferidas

      $injector.invoke(function($compile, $rootScope) {
        //
      });
    
  • inline

      injector.invoke(['$compile', '$rootScope', function(obfCompile, obfRootScope) {
          //
      }]);
    
  • Anotadas

      var tmpFn = function(obfuscatedCompile, obfuscatedRootScope) {
        //
      };
      tmpFn.$inject = ['$compile', '$rootScope'];
      $injector.invoke(tmpFn);
    

Idea creativa!

app.config(function ($routeProvider) {
    $routeProvider
        .when('/services/:serviceId/service', {
            controller: 'ServiceCtrl',
            resolve: {
                service: function ($route, services) {
                    return services.get($route.current.params.serviceId);
                }
            }
        });
});

app.controller('ServiceCtrl', function ($scope, service) {
    $scope.service = service;
});

Advanced

  • La etiqueta ng-app construye un inyector
var injector = angular.element($("body")).injector();

var $templateCache = injector.get("$templateCache");

$templateCache.get("popover/docs/popover.tpl.demo.html");

Enlace

Lista de la compra 2

Objetivos

- vistas con el router de angular
- comunicación rest con angular resources
- paginación con angular bootstrap ui
- filtros
- animaciones

Extra:
- bus de comunicación
- asincronía javascript mediante promesas

Enlace

Routing

app.config(function ($routeProvider) {
  $routeProvider
    .when('/', {
        templateUrl: 'lista.html',
        controller: 'ListaCtrl'
    })
    .otherwise({
      redirectTo: '/'
    });
});
.when('/:id', {
    templateUrl: 'editar.html',
    controller: 'EditarCtrl',
    resolve: {
        ingrediente: function ($route, Ingrediente) {
            return Ingrediente
                .get({id: $route.current.params.id})
                .$promise;
        }
    }
})

Servicios REST

app.factory('services', function () {
  return $resource('/services/:serviceId', {serviceId:'@id'});
});
  • Index

      var services = Services.query(function () {
          console.log("hemos recibido los servicios!", services);
      });
    
  • Create

      Service.$save(function(service) {
         $location.url('/');
      });
    
  • Update

      Service.$update(function(service) {
         $location.url('/');
      });
    
  • Get
      Service.get({serviceId:123}, function(service) {
         $scope.service = service;
       });
    

Enlace

AngularStrap y ngStyle

Objetivos

- templates con ng-include
- ventanas modales y popovers con AngularStrap

angularStrap

Enlace

Enlace

Angular Translate

Objetivos

- Angular Translate

Angular Translate

Enlace

Enlace

Masonry

Objetivos

- Galeria con Masonry

Angular Masonry

Enlace

Enlace

Directivas

Lleva un momento inevitable en el que:

  • Necesitas manipular el DOM directamente
  • Necesitas refactorizar codigo repetido
  • Necesitas crear HTML para diseñadores

Compilación

  • Compila el HTML analizando el DOM devuelto por el navegador
  • Compara cada elemento, atributo, comentario o class css contra una lista de directivas registradas
  • Si la directiva no está compilada, llama a la función compile (esta devolverá la función link)
  • Llama a la función link de la directiva
      link: function(scope, element, attrs, ngModelController) { ... }
    
  • Crea un scope y asigna el scope a cada directiva

¿Cómo indicamos que es una directiva?

<my-directive></my-directive>
<input my-directive>
<!-- directive: my-directive-->
<input class="my-directive">

Angular normalizará los nombres:

  • Elimina x- y el data- de los elementos y atributos
  • Convierte los nombres seperados por : - _ a camelCase

Scopes y directivas

  • reutilizar el scope

      scope: false
    
  • nuevo scope

      scope: true
    
  • Aislar

      {
          interpolado: "@",
          databind: "=",
          expression: "&"
      }
    

Scopes y directivas

¿Como le indicamos si debe ser un nombre, un atributo o una clase?

Convención

- Elemento si tomará el control del template
- Atributo si "decora" el elemento.

La restricción se define con:

- 'A' attribute name
- 'E' element name
- 'C' class name

require: '?ngModel' or '^ngModel'

myModule.directive('button', function() {
    return {
        restrict: 'E',
        compile: function(element, attributes) {
            element.addClass('btn');
            if ( attributes.type === 'submit' ) {
                element.addClass('btn-primary');
            }
            if ( attributes.size ) {
                element.addClass('btn-' + attributes.size);
            }
        }
    };
});
describe('button directive', function () {
    var $compile, $rootScope;
    beforeEach(module('directives.button'));
    beforeEach(inject(function(_$compile_, _$rootScope_) {
        $compile = _$compile_;
        $rootScope = _$rootScope_;
    }));
    it('adds a "btn" class to the button element', function() {
        var element = $compile('<button></button>')($rootScope);
        expect(element.hasClass('btn')).toBe(true);
    });
});
myModule.directive('validateEquals', function() {
    return {
        require: 'ngModel',
        link: function(scope, elm, attrs, ngModelCtrl) {
            function validateEqual(myValue) {
                var valid = (myValue === scope.$eval(attrs.validateEquals));
                ngModelCtrl.$setValidity('equal', valid);
                return valid ? myValue : undefined;
            }
            ngModelCtrl.$parsers.push(validateEqual);
            ngModelCtrl.$formatters.push(validateEqual);
            scope.$watch(attrs.validateEquals, function() {
                ngModelCtrl.$setViewValue(ngModelCtrl.$viewValue);
            });
        }
    };
});
describe('validateEquals directive', function() {
    var $scope, modelCtrl, modelValue;
    beforeEach(inject(function($compile, $rootScope) {
        ...
        modelValue = $scope.model = {};
        modelCtrl = $scope.testForm.testInput;
        ...
    }));
    ...
    describe('model value changes', function() {
        it('should be invalid if the model changes', function() {
            modelValue.testValue = 'different';
            $scope.$digest();
            expect(modelCtrl.$valid).toBeFalsy();
            expect(modelCtrl.$viewValue).toBe(undefined);
        });
        it('should be invalid if the reference model changes', function() {
            modelValue.compareTo = 'different';
            $scope.$digest();
            expect(modelCtrl.$valid).toBeFalsy();
            expect(modelCtrl.$viewValue).toBe(undefined);
        });
        it('should be valid if the modelValue changes to be the same as the reference', function() {
            modelValue.compareTo = 'different';
            $scope.$digest();
            expect(modelCtrl.$valid).toBeFalsy();
            modelValue.testValue = 'different';
            $scope.$digest();
            expect(modelCtrl.$valid).toBeTruthy();
            expect(modelCtrl.$viewValue).toBe('different');
        });
});