3 de Diciembre

de 2015

Controladores y Componentes en

Angular 1.4, 1.5 y 2.0

¿Quien soy?

Lorenzo, un profesor de FP

¿Quieres que mis alumnos hagan las prácticas en tu empresa?
¡Ponte en contacto conmigo!

Mareando la perdiz

7 formas distintas

de hacer los mismo

con un controlador 

en AngularJS

Lunes

ng-controller y $scope

ng-controller y $scope

<div ng-controller="FacturaController">
	<h3>Dentro del controlador Factura</h3>
	Empresa:{{factura.empresa}}<br>
	<div ng-controller="LineaFacturaController">
		<h3>Dentro del controlador LineaFactura</h3>
		Empresa:{{factura.empresa}}<br>    
		Concepto:{{lineaFactura.concepto}}<br>
                <button ng-click="avisar(lineaFactura.concepto)">Avisar</button>  
	</div>
</div>
app.controller("FacturaController",FacturaController);
function FacturaController($scope) {
    $scope.factura={
        id:4,
        importeTotal:34,
        empresa:"Persianas López"
    };
    $scope.tiposIVA={
        general:21,
        reducido:10,
        superreducido:4
    }

    $scope.avisar=function(message) {
        alert(message);
    }
}

app.controller("LineaFacturaController",LineaFacturaController);
function LineaFacturaController($scope) {
    $scope.lineaFactura={
        id:45,
        cantidad:2,
        precioUnitario:5,
        concepto:"Mosquitera"
    }
}

ng-controller y $scope

Características

Sencillo: Solo el controlador y el HTML

 

 

$scope soup: No podemos saber cada propiedad de donde viene. Ej: "factura" , "lineaFactura", "avisar"

Código muy acoplado

 

Martes

controller as

controller as

<div ng-controller="FacturaController as facturaController">
	<h3>Dentro del controlador Factura</h3>
	Empresa:{{facturaController.factura.empresa}}<br>
	<div ng-controller="LineaFacturaController as lineaFacturaController">
		<h3>Dentro del controlador LineaFactura</h3>
		Empresa:{{facturaController.factura.empresa}}<br>    
		Concepto:{{lineaFacturaController.lineaFactura.concepto}}<br>
                <button ng-click="facturaController.avisar(lineaFacturaController.lineaFactura.concepto)">Avisar</button>  
	</div>
</div>
app.controller("FacturaController",FacturaController);
function FacturaController() {
    this....;
}

app.controller("LineaFacturaController",LineaFacturaController);
function LineaFacturaController() {
    this.....;
}

Características

Ya no usamos "$scope" sino "this"

Ya sabemos a que controlador pertenece cada propiedad

Ya no hay herencia de "$scope". Está aislado

 

 

Usamos un "prefijo" delante de las propiedades

Dependemos de que el controlador padre exista y que tenga la propiedad exacta que necesitamos. Ej: "facturaController" con la propiedad "factura"

Mas complejo el usar "$watch"

Código acoplado

Miercoles

Directiva - componente

Componentes

Entrada

Salida

  • Independientes del contexto
  • Reusables
  • Parámetros de Entrada
  • Parámetros de Salida
  • Ocultación de la información

Componentes

Directivas con template

Ej:  Calendario , Botón , Arbol , etc.

Directivas

Directivas sin template

Ej: ng-repeat , ng-hide , etc.

<!-- directiva -->
<mi-factura></mi-factura>
<!-- template de <mi-factura> -->
<h3>Dentro de la directiva Factura</h3>
Empresa:{{miFactura.factura.empresa}}<br>
<mi-linea-factura 
     factura='miFactura.factura'
     on-button="miFactura.avisar(mensaje)"
></mi-linea-factura>
<!-- template de <mi-linea-factura> -->
<h3>Dentro de la directiva LineaFactura</h3>
Empresa:{{miLineaFactura.factura.empresa}}<br>
Concepto:{{miLineaFactura.lineaFactura.concepto}}<br>
<button ng-click="miLineaFactura.onButton({mensaje:miLineaFactura.lineaFactura.concepto})">Avisar</button>  

Componentes

app.directive("miFactura",FacturaDirective);
function FacturaDirective() {
    return {
        templateUrl :"mi-factura-tpl.html",
        controller:function() {
            this....
        },
        controllerAs:"miFactura",
        scope:{
        },
        bindToController:{
        }
    }
}
app.directive("miLineaFactura",LineaFacturaDirective);
function LineaFacturaDirective() {
    return {
        templateUrl :"mi-linea-factura-tpl.html",
        controller:function() {
            this....
        },
        controllerAs:"miLineaFactura",
        scope:{
        },
        bindToController:{
            factura:"=",
            onButton:"&"
        }
    }    
};

Características

Uso de parámetros: Ya no dependemos para nada del controlador padre

Usamos un "prefijo" delante de las propiedades que ahora es innecesario

Un poco "verbose" para crear el componente

Características

Acceso a información privada

.....
controller:function() {
    ......
    this.privateValue="s3cret";
}
......
.....
require:"^miFactura",
link: function(scope, element, attrs, miFacturaController) {
    console.log("Valor privado:"+miFacturaController.privateValue)
} 
......

El "this" del controlador es un API pública

para ser usada por otra directiva.

Directiva "miFactura"

Directiva "miLineaFacura"

Características

Problema con el prefijo si reusamos el HTML 

<linea-factura-update></linea-factura-update> //Prefijo lineaFacturaUpdate
<linea-factura-insert></linea-factura-insert> //Prefijo lineaFacturaInsert
<linea-factura-delete></linea-factura-delete> //Prefijo lineaFacturaDelete
<!-- template de <mi-linea-factura> -->
<h3>Dentro de la directiva LineaFactura</h3>
Empresa:{{lineaFactura.factura.empresa}}<br>
Concepto:{{lineaFactura.lineaFactura.concepto}}<br>
<button ng-click="vm.onButton({mensaje:lineaFactura.lineaFactura.concepto})">Avisar</button>  

Varias directivas que usan la misma plantilla

Plantilla con prefijo genérico llamado "lineaFactura"

El prefijo ya no es el nombre de la directiva sino algo "generico" . 

Jueves

component Angular 1.5

app.component("miFactura", {
    templateUrl: "mi-factura-tpl.html",
    controller: function () {
        this...
    },
    bindings: {
    }
});


app.component("miLineaFactura", {
    templateUrl: "mi-linea-factura-tpl.html",
    controller: function () {
        this...
    },
    bindings: {
        factura: "=",
        onButton:"&"
    }
});

Component Angular 1.5

Características

Simplificamos la creación

Es lo "moderno"

 

 

 

Seguimos usando un prefijo inútil

Seguimos accediendo a datos privados

Sigue siendo mas complejo usar el "watch"

 

Viernes

component en Angular 2

@Component({
  selector: 'mi-linea-factura',
  inputs: ['factura'],
  outputs: ['button']
})
@View({
  template: `
    <h3>Dentro de la directiva LineaFactura</h3>
    Empresa:{{factura.empresa}}<br>
    Concepto:{{lineaFactura.concepto}}<br>
    <button (click)="button(lineaFactura.concepto)">Avisar</button>  
  `
})
class miLineaFactura {
  this...
}

component Angular 2

Características

Prácticamente ventajas .......

 

 

 

Aun no disponible

¿TypeScript?

Sábado

Componente con $scope

app.directive("miLineaFactura",LineaFacturaDirective);
function LineaFacturaDirective() {
    return {
        templateUrl :"mi-linea-factura-tpl.html",
        controller:['$scope',function(vm) {
            vm...
            this...
        }],
        scope:{
            factura:"=",
            onButton:"&"
        }
    }    

};

¿Y si a "$scope" lo llamo "vm"?

Componente con $scope

<!-- directiva -->
<mi-factura></mi-factura>
<!-- template de <mi-factura> -->
<h3>Dentro de la directiva Factura</h3>
Empresa:{{factura.empresa}}<br>
<mi-linea-factura 
     factura='factura'
     on-button="avisar(mensaje)"
></mi-linea-factura>
<!-- template de <mi-linea-factura> -->
<h3>Dentro de la directiva LineaFactura</h3>
Empresa:{{factura.empresa}}<br>
Concepto:{{lineaFactura.concepto}}<br>
<button ng-click="onButton({mensaje:lineaFactura.concepto})">Avisar</button>  

Ya no necesitamos el prefijo

Características

Eliminamos los prefijos superfluos "miFactura" y miLineaFactura"

Es mas fácil usar "$watch" ,etc.

Si otra directiva usa nuestro controlador , no exponemos las interioridades del $scope sino solo las del "this". Ej:   require: "^miFactura"

 

Mezclamos $scope y this, ¿pero quizás eso sea bueno?

Domingo

Pantallas

¿Es la arquitectura de componentes escalable?

En proyectos grandes

(no hablo de peticiones/s)

Ejemplo de aplicación con 100 entidades

CRUD por

cada entidad

400 Rutas

400 Componentes

400 Controladores

100 HTML

Total: 1300 Artefactos

No es escalable, demasiados artefactos

(13 por Entidad)

Ejemplo con Factura

Ruta        ==> /factura/new
Componente  ==> <factura-new>
Controlador ==> FacturaNewController
HTML        ==> factura-detail.html
Ruta        ==> /factura/update/:id
Componente  ==> <factura-update>
Controlador ==> FacturaUpdateController
HTML        ==> factura-detail.html
Ruta        ==> /factura/delete/:id
Componente  ==> <factura-delete>
Controlador ==> FacturaDeleteController
HTML        ==> factura-detail.html

Primera Mejora

4 Controladores genéricos

GenericNewController
GenericUpdateController
GenericDeleteController
GenericViewController

400 Rutas

400 Componentes

    4 Controladores

100 HTML

Total: 904 Artefactos

¿Son estos 400 ( 4 x 100) componentes  realmente reusables?

<factura-new>
<factura-update>
<factura-delete>
<factura-view>

<linea-factura-new>
<linea-factura-update>
<linea-factura-delete>
<linea-factura-view>

<albaran-new>
<albaran-update>
<albaran-delete>
<albaran-view>

.......
.......
<linea-factura-view-1> 


<!-- Plantilla HTML -->  
<h1>{{lineaFactura.concepto}}</h1>
importe:{{lineaFactura.cantidad*lineaFactura.precioUnitario}} €

NO mucho,

suele cambiar el HTML

<linea-factura-view-2>  


<!-- Plantilla HTML -->   
concepto: {{lineaFactura.concepto}}<br>
cantidad: {{lineaFactura.cantidad}}<br>
precioUnitario:{{lineaFactura.precioUnitario}}<br>

Componentes

Directivas con template (reusable)

Ej:  Calendario , Botón , Arbol , etc.

Directivas

Directivas sin template (reusable)

Ej: factura-new, factura-delete, factura-update

Pantallas

Un componente con HTML NO reusable

Ej: ng-repeat , ng-hide , etc.

<generic-component  controller="LineaFacturaViewController" [factura]="factura"  >   
     <h1>{{lineaFactura.concepto}}</h1>
     importe:{{lineaFactura.cantidad*lineaFactura.precioUnitario}} €
</generic-component>



<generic-component  controller="LineaFacturaViewController" [factura]="factura"  >  
    concepto: {{lineaFactura.concepto}}<br>
    cantidad: {{lineaFactura.cantidad}}<br>
    precioUnitario:{{lineaFactura.precioUnitario}}<br>
</generic-component>

<generic-component>

Configurable

El controlador

El HTML

app.controller("LineaFacturaViewController",LineaFacturaViewController);
function LineaFacturaViewController($scope) {
}

Segundo paso

1 Componente genérico

<generic-component>

400 Rutas*

    1 Componentes

    4 Controladores

100 HTML

Total: 505 Artefactos

*Es fácil hacer una función crear las 400 automáticamente 

Caracteristicas

Muy sencillo de usar

No necesitamos los prefijos

El $scope está aislado

Se usan parámetros

Reusable, no dependemos del controlador "padre"

 

 

 

Usamos el $scope o "vm" :-)

¿Migración a AngularJS?

 

<generic-component controller="GenericUpdateController" entity="Factura" >
    <h3>Dentro del controlador Factura</h3>
    Empresa:{{factura.empresa}}
    <generic-component  controller="LineaFacturaController" [factura]="factura"  (on-button)="avisar(mensaje)" >
         <h3>Dentro del controlador LineaFactura</h3>
         Empresa:{{factura.empresa}}<br>    
         Concepto:{{lineaFactura.concepto}}<br>
         <button ng-click="onButton({mensaje:lineaFactura.concepto})">Avisar</button>
    </generic-component>
</generic-component>

Pantalla

Implementación 

 

<generic-component>

app.directive('genericComponent', genericComponent);
function genericComponent() {
    return {
        restrict: 'E',
        scope: {
        },
        priority: 500,
        transclude: true,
        controller: "@",
        link: function (scope, iElement, iAttrs, controller, $transcludeFn) {
            $transcludeFn(scope, function (clone) {
                iElement.append(clone);
            });
        }
    };
}

directiva "generic-component"

Acceder a los parametros

function LineaFacturaController($scope,$attrs) {

    $scope.factura=$attrs.factura;

}
<generic-component 
  controller="LineaFacturaController" 
  [factura]="factura"  
  (on-button)="avisar(mensaje)" 
>
    <h3>Dentro del controlador LineaFactura</h3>
    Empresa:<input ng-model="factura.empresa"> <br>    
    Concepto:<input ng-model="lineaFactura.concepto"> <br>
    <button ng-click="onButton({mensaje:lineaFactura.concepto})">Avisar</button>
</generic-component>

Usando "$attrs" podemos acceder a los parámetros

Problema

¿Como sabemos que tipo de binding hacer?

  • "=" : Debemos poner una expresión del $scope padre
  • "&" : Es un estamento a ejecutar
  • "@" : Debemos usar "{{}}"

Antes en las directivas:

Usamos "$parse" o "$interpolate" según el tipo de binding

Solución

Seguir la misma estrategia que Angular 2.0

  • "=" : Usar "[ ]"                                         [factura] = "factura"
  • "&" : Usar "( )"                                         (on-button) = "avisar()" 
  • "@" : Si no tiene ni "[ ]" ni "( )".            factura="{{factura}}"  
<generic-component 
  controller="LineaFacturaUpdateController" 
  [factura]="factura"  
  (on-button)="avisar(mensaje)" 
>
    <h3>Dentro del controlador LineaFactura</h3>
    Empresa:<input ng-model="factura.empresa"> <br>    
    Concepto:<input ng-model="lineaFactura.concepto"> <br>
    <button ng-click="onButton({mensaje:lineaFactura.concepto})">Avisar</button>
</generic-component>
<generic-component 
  controller="LineaFacturaUpdateController" 
  factura="{{factura}}"  
  (on-button)="avisar(mensaje)" 
>
    <h3>Dentro del controlador LineaFactura</h3>
    Empresa:<input ng-model="factura.empresa"> <br>    
    Concepto:<input ng-model="lineaFactura.concepto"> <br>
    <button ng-click="onButton({mensaje:lineaFactura.concepto})">Avisar</button>
</generic-component>

Binding "=" de "factura" y usamos $parse

Binding "@" de "factura" y usamos $interpolate

Problema

Es una chapuza hacer el binding manualmente

function LineaFacturaUpdateController($scope,$attrs,$parse,$interpolate) {

    $scope.factura=      $parse($attrs.factura)($scope.$parent);
    $scope.factura=$interpolate($attrs.factura)($scope.$parent);

}

Solución

app.decorator("$controller", function ($delegate, $interpolate, $parse) {

        var originalFn = $delegate;

        newFn = function (expression, locals, later, ident) {

            if (locals.$element[0].nodeName==="GENERIC-COMPONENT") {
                bindAttrsToScope(locals.$scope, locals.$attrs);
                expression=locals.$scope.controller;
            }

            return originalFn(expression, locals, later, ident);
        };

        return newFn;
);

Decorar el servicio "$controller"

Tip:Factoria de Controladores

app.decorator("$controller", function ($delegate, $interpolate, $parse) {
        var originalFn = $delegate;
        newFn = function (expression, locals, later, ident) {
            expression=controllerFactory(expression,locals.$attrs);
            return originalFn(expression, locals, later, ident);
        };

        return newFn;
);

¡Es posible decidir en tiempo de ejecución el controlador a usar dependiendo de si existe o no cada controlador!

GenericViewController

LineaFacturaViewController

LineaFacturaViewLargeController

Definir datos en la ruta

En los controladores no usar

$routeParams , $stateParams, etc.

En el resolve añadir el objeto "controllerParams"

$routeProvider.when('/factura/:idFactura', {
  templateUrl: "factura.html",
  controller: "GenericUpdateController",
  resolve:{
    controllerParams:function($route) {
      return {
        entity:"Factura",
        id:$route.current.params.idFactura
      }
    }
  }
});
$routeProvider.when('/mifactura', {
  templateUrl: "factura.html",
  controller:"GenericViewController",
  resolve:{
    controllerParams:function() {
      return {
        entity="Factura",
        id:10
      }
    }
  }
});
<generic-component  controller="{{controller}}" entity="{{entity}}" id="{{id}}" >
    <!-- Aqui el HTML --->
</generic-component>

factura.html

Gracias

https://github.com/logongas/angularvalencia-controladores

@logongas

lorenzo.profesor@gmail.com

¿Preguntas?

Ejemplos en:

Controladores y Componentes en Angular 1.4, 1.5 y 2.0

By Lorenzo Gonzalez Gascón

Controladores y Componentes en Angular 1.4, 1.5 y 2.0

Como usar un controlador y/o componente de 7 formas distintas. El problema de "$scope hell"

  • 5,236