viernes, 30 de diciembre de 2016

Angular 2. Formularios. Entrega 5: Templates (4). Directivas

Anteriormente en la versión 1 de Angular, había más de 70 directivas, además la comunidad contriubuyó con muchísimas más. Pero se cree que no hace falta tantas directivas. Veamos las más importantes:

1. NgClass vs class binding

Es la mejor opción para agregar o quitar clases CSS dinámicamente.

Veamos el primer ejemplo donde utilizamos class binding en vez de NgClass, para modificar una sola clase.

!-- toggle the "special" class on/off with a property -->
<div [class.special]="isSpecial">The class binding is special</div>
En cambio con NgClass, podemos asignar varia clases a la vez. Veamos la función setClasses() que da varios valores de clase:

setClasses() {
  let classes =  {
    saveable: this.canSave,      // true
    modified: !this.isUnchanged, // false
    special: this.isSpecial,     // true
  };
  return classes;
}
Y en el template las podemos utilizar con NgClass:
<div [ngClass]="setClasses()">This div is saveable and special</div>

2. NgStyle vs style binding

Al igual que antes, vemaos un ejemplo de style binding, donde solo podemos assignar un estilo a la vez
<div [style.font-size]="isSpecial ? 'x-large' : 'smaller'" >
  This div is x-large.
</div>
En cambio con NgStyle podemos asignar varios estilos a la vez, como en el ejemplo anterior, creamos en el componente, una variable de tipo estructura que tenga los siguientes atributos, font-style, font-weight y font-size, y mediante el operador ":" les asignamos valores mediante el operador ternario (condicion ? valor_si_se_cumple : valor_si_no_se_cumple)

setStyles() {
  let styles = {
    // CSS property names
    'font-style':  this.canSave      ? 'italic' : 'normal',  // italic
    'font-weight': !this.isUnchanged ? 'bold'   : 'normal',  // normal
    'font-size':   this.isSpecial    ? '24px'   : '8px',     // 24px
  };
  return styles;
}
Y ahora en el template asignamos varios estilos a la vez.

<div [ngStyle]="setStyles()">
  This div is italic, normal weight, and extra large (24px).
</div>

3. NgIf vs visibilidad con clases y estilos

Hasta ahora, las directivas NgClass, NgStyle, se trascribían [ngClass ] y [ngStile] en el template , pero la directiva NgIf se escribe *ngIf en el template, y no nos olvidemos del asterisco.

Si queremos quitar elementos (y sus hijos)del DOM, utilizaremos NgIf, pero si no los queremos quitar pero si ocultar (hacerlos invisibles) podemos aplicar una clase o estilo invisible.
<div *ngIf="currentHero">Hello, {{currentHero.firstName}}</div>
Aquí si no existe currentHero, no muestra nada. Veamos otro ejemplo:
<!-- because of the ngIf guard
    `nullHero.firstName` never has a chance to fail -->
<div *ngIf="nullHero">Hello, {{nullHero.firstName}}</div>

<!-- Hero Detail is not in the DOM because isActive is false-->
<hero-detail *ngIf="isActive"></hero-detail>
Pero codemos ocultarlo (y no eliminarlo del DOM con clases)
<!-- isSpecial is false -->
<div [class.hidden]="!isSpecial">Show with class</div>


4. NgSwitch (cuidado con los *)

Es similar a la anterior, pero cuidado con los asteriscos y corchetes, ya que se utilizan 3 subdirectivas

1. [ngSwitch]
2. *ngSwitchCase
3. *ngSwitchDefault

Veamos un ejemplo en el template:
<span [ngSwitch]="toeChoice">
  <span *ngSwitchCase="'Eenie'">Eenie</span>
  <span *ngSwitchCase="'Meanie'">Meanie</span>
  <span *ngSwitchCase="'Miney'">Miney</span>
  <span *ngSwitchCase="'Moe'">Moe</span>
  <span *ngSwitchDefault>other</span>
</span>

5.NgFor e índices. Problemas de rendimiento.

Sirve para hacer repeticones. Se puede aplicar a un simple div o a un componente:

div *ngFor="let hero of heroes">{{hero.fullName}}</div>
<hero-detail *ngFor="let hero of heroes" [hero]="hero"></hero-detail>
OJO: Con el let hero del for creamos una variable de entrada del template, que NO es lo mismo que una variable de referencia del template

Tenemos las siguientes variables dentro del bucle NgFor:
1. index: Contador desde 0 en el bucle
2. first: si es el primero
3. last: si es el último
4. even: si es par
5. odd: si es impar

Aquí mostramos el nombre del héroe y sumamos 1 para que nos muestre el contador desde 1 y no desde 0:
<div *ngFor="let hero of heroes; let i=index">{{i + 1}} - {{hero.fullName}}</div>
Pueden surgir problemas de rendimiento, normalmente en listas largas. Una inserción o remoción de un elemento, puede disparar manipulaciones del DOM en cascada.
Si pedimos otra vez la lista de heroes desde el servidor, borra la lista antigua y la sustituye por la nueva. En cambio, si le aportamos una función que le indica que un héroe es el mismo si tiene el mismo id, entonces, angular no borrara los cambios efectuados desde el último refresco.

Veamos la función
trackByHeroes(index: number, hero: Hero) { return hero.id; }
Y también como utilizarla en el template con la clausula trackBy:
<div *ngFor="let hero of heroes; trackBy:trackByHeroes">({{hero.id}}) {{hero.fullName}}</div>




Angular 2. Formularios. Entrega 4: Templates (3). Binding bidireccional. NgModel

Vamos a ver el dindig bidireccional con la nomenclatura banana box y también con NgModel.


1. Two-way binding con banana box


La nomenclatura banana box [( )] , combina los corchetes de binding de propiedades [ ], con  los paréntesis del binding de eventos ( ).

Para ello se deberá utilizar las anotaciones @Input() de la variable del componente para que sea accedible a un componente padre, y también la aontación @Output() para exportar el evento.

Vale la pena mirar las explicaciones de Angular2 pero sobre todo mirar el live example.

No me voy a extender mas en este tema, ya que es mejor utilizar NgModel.


2. Two-way binding con NgModel


NgModel es la manera mas aconsejada del 2-binding:
<input [(ngModel)]="currentHero.firstName">
Y no hay que estar atento al evento de cambio de valores, ya que NgModel lo actualiza inmediatamente, sin tener que preocuparnos por interceptar eventos.


Pero requiere que se importe FormsModule en el módulo.
import { NgModule } from '@angular/core';
import { BrowserModule }  from '@angular/platform-browser';
import { FormsModule } from '@angular/forms';

import { AppComponent } from './app.component';

@NgModule({
  imports: [
    BrowserModule,
    FormsModule
  ],
  declarations: [
    AppComponent
  ],
  bootstrap: [ AppComponent ]
})
export class AppModule { }
Pero a veces puede ser interesante interceptar los eventos de cambio del modelo,por ejemplo para pasar a mayúsculas. Entonce quedaría el template anterior

<input
  [ngModel]="currentHero.firstName"
  (ngModelChange)="setUpperCaseFirstName($event)">




Angular 2. Formularios. Entrega 3: Templates (2). Binding de Atributos, clases, estilos y eventos

Para no set tan enfarragosos como en la entrada anterior, solo veremos los bindings de los atributos, clases, estilos y eventos.

1. Binding de Atributos


Como ya vimos, hacer attribute binding es una excepción. Es decir, es mejor cambiar las PROPIEDADES del DOM que los atributos HTML.

Solamente, deberemos utilizar attribute binding cuando NO EXISTA dicha propiedad del DOM disponible para ser cambiada.

Como ejemplos de estos atributos tenemos: ARIA, SVG, y "table span". Son atributos puros y no existen propiedades a las que se pueda hacer un bind.

Si queremos pues cambiar dichos atributos, no se pueden colocar directamente si no a través de prefijo att seguido de un punto (.) y el nombre del atributo. Veamos un ejemplo



<table border=1>
  <!--  expression calculates colspan=2 -->
  <tr><td [attr.colspan]="1 + 1">One-Two</td></tr>

  <!-- ERROR: There is no `colspan` property to set!
    <tr><td colspan="{{1 + 1}}">Three-Four</td></tr>
  -->

  <tr><td>Five</td><td>Six</td></tr>
</table>

Aquí observamos que el texto entre comentarios que daría un error ya que NO encuentra la propiedad del DOM colspan.

Otro ejemplo vemos como cambiatr atributos de ARIA.

<!-- create and set an aria attribute for assistive technology -->
<button [attr.aria-label]="actionName">{{actionName}} with Aria</button>

2. Binding de clases.


Podemos asignar clases de 2 maneras:

1. Asignando [class]="variable que guarda el nombre de la clase".
2. Asignando [class.nombreClase]="variable booleana que activa o desactiva"

En el primer caso asignamos un único valor de clase, resetando otros valores de clase asignados previamente, mientras que en el segundo caso, si la variable booleana toma el valor false, desasignamos dicha clase (y en caso contrario la asignamos).

Veamos el primer caso

<!-- reset/override all class names with a binding  -->
<div class="bad curly special"
     [class]="badCurly">Bad curly</div>
Para el segundo caso


<!-- toggle the "special" class on/off with a property -->
<div [class.special]="isSpecial">The class binding is special</div>

<!-- binding to `class.special` trumps the class attribute -->
<div class="special"
     [class.special]="!isSpecial">This one is not so special</div>
Para manejar múltiles clases al mismo tiempo es aconsejable utilizar la directiva NgClass.


3. Binding de estilos.


Se definen asignando  [style.style.property]="expresión que devuelve un string"
Veamos ejemplos:
<button [style.color] = "isSpecial ? 'red': 'green'">Red</button>
<button [style.background-color]="canSave ? 'cyan': 'grey'" >Save</button>
<button [style.font-size.em]="isSpecial ? 3 : 1" >Big</button>
<button [style.font-size.%]="!isSpecial ? 150 : 50" >Small</button>
Pero se prefiere la directiva NgStyle.



4. Binding de eventos


Esta parte es un poco mas complicada. Aquí lo que se pretende es avisar al componente que se ha producido un evento, y la respuesta será ejecutar una función del componente.

Se  pueden utilizar en principio 2 nomenclaturas:

1.  (evento)="funcion_del_Componente() o expresión"
2.  on-evento="funcion_del_Componente() o expresion"

Siendo evento, el nombre del evento del elemento del DOM principalmente, o de una directiva. En un botón sería "click".
Veamos los ejemplos:
<button (click)="onSave()">Save</button>
<button on-click="onSave()">On Save</button>

OJO: Angular mira primero si existe un evento con el mismo nombre de una directiva, y si lo encuentra le da prioridad. Se verá mas adelante.

4.1 $event 

En un binding de evento, Angular establece un manejador de eventos (event handler) para el evento específico.

Cuando salta el evento, se ejecuta una función del componente, pero la información relacionada con el evento se guarda en un objeto llamado $event.

Si el evento es un evento nativo del DOM, entonces $event será del tipo evento del DOM, peró si el evento es de una directiva, el objeto será del tipo que haya definido internamente la directiva.

Veamos un ejemplo del primer caso, donde se actualiza el firstname del Hero a medida que escribimos.
<input [value]="currentHero.firstName"
       (input)="currentHero.firstName=$event.target.value" >


4.2 Eventos personalizados con EventEmitter

Para crear un evento en un componente, este evento debe ser del tipo EventEmitter. A continuación hay que crear una función (que se hace referencia en el template) que contenga el método "emit" con un parámetro.

En el ejemplo creamos un objeto deleteRequest del tipo EventEmitter<Hero>. Y el <Hero> determina el tipo de parametro a pasar al método emit.


// This component make a request but it can't actually delete a hero.
deleteRequest = new EventEmitter<Hero>();

delete() {
  this.deleteRequest.emit(this.hero);
}
Y en el template, habrá un botón que activará la función delete con el evento click que "emitirá un objeto de tipo "Hero".

<button (click)="delete()">Delete</button>
Pero por otra parte, tal como se dice en la información de angular2, podria haber un componente padre que hace un bind al objeto deleteRequest de nuestro componente anterior y recoge el objeto tipo Hero que se ha emitido con el método emit (a traves de $event) para proponer su borrado.
<hero-detail (deleteRequest)="deleteHero($event)" [hero]="currentHero"></hero-detail>



jueves, 22 de diciembre de 2016

Angular 2. Formularios. Entrega 2: Templates (I). Interpolación, propiedades y atributos. Binding

Veamos que nos podemos encontrar en una plantilla

1. Sintaxix moustache {{}} o Interpolación

Se puede usar en

1 Texto fuera de las tags de HTML. Permitiendo evaluación de expresiones simples
<p>My current hero is {{currentHero.firstName}}</p>
<!-- "The sum of 1 + 1 is 2" -->
<p>The sum of 1 + 1 is {{1 + 1}}</p>

2. Para dar valores a atributos de ciertas tags de HTML
<img src="{{heroImageUrl}}" style="height:30px">


2. Expresiones y sentencias del Template

Hay que tener en cuenta que no nos podemos referir a ningún componente del global namespace. Por ejemplo, no se puede tener acceso a window ni document, ni a console.log ni Math.max. Solamente podemos acceder a métodos y atributos del componente asociado.


Dirección de los datos Sintaxis Tipo de Bind
Modelo -> Vista
(único sentido)
{{expression}}
[target] = "expression"
bind-target = "expression"
Interpolation,
Property,
Attribute,
Class,
Style
Vista -> Modelo
(único sentido)
(target) = "statement"
on-target = "statement"
Event
Modelo <-> Vista 
(doble sentido)
[(target)] = "expression"
bindon-target = "expression"
Doble sentido
Donde se sustituye "target" por una propiedad o evento del DOM. Como se ve, hay una doble nomenclatura par decir lo mismo: 

  • [propiedad] equivale bind-propiedad 
  • (propiedad) equivale on-propiedad
  • [(propiedadequivale bindon-propiedad
Pero vamos a distinguir entre:

  • Atributos (que se definen en el HTML). No varían nunca. En un control input tipo texto con un valor inicial (atributo value="A"). Si depués de cambiar el usario el valor a "B" y hacemos "input.getAttributte('value')", nos devuelve "B" y NO "A". En cambio el DOM si tiene el valor correcto de "B".
  • Propiedades del DOM (Document Object Model). Pueden variar. No es lo mismo el atributo disabled de un boton (se incluye disabled o no se incluye en el HTML) y otra cosa es la propiedad disabled="true" del DOM
Como hemos dicho antes, solo podemos parametrizar PROPIEDADES y EVENTOS del DOM, y muy raramente atributos HTML.
Pero las propiedades y eventos se pueden especificar en elementos, componentes y directivas.

Veamos algunos ejemplos:



Elemento
Ejemplo
Propiedad de elemento <img [src] = "heroImageUrl" >
Propiedad de componente <hero-detail [hero]="currentHero" ></hero-detail >
Propiedad de directiva <div [ngClass] = "{selected: isSelected}"></div>
Evento de elemento <button (click) = "onSave()">Save</button>
Evento de componente <hero-detail (deleteRequest)="deleteHero()"></hero-detail>
Evento de directiva <div (myClick)="clicked=$event">click me</div>
Evento y propiedad two way binding <input [(ngModel)]="heroName">
Atributo (EXCEPCION) <button [attr.aria-label]="help">help</button>
Propiedad de clase <div [class.special]="isSpecial">Special</div>
Propiedad de estilo <button [style.color] = "isSpecial ? 'red' : 'green'">


Observaciones:

1. Si OMITIMOS los CORCHETES, no se evalua la parte derecha de la igualdad y se supone esta sefunda parte un string, con lo que si hacemos <hero-detail hero="currentHero"></hero-detail> sin cochetes en hero, se asignará al objeto hero el struing "currentHero" y NO el objeto llamado currentHero.
2. Podemos omitir los corchetes cuando se verifican todas estas premisas a la vez:

  2.1 El "target" acepta un valor string
  2.2 El string es un valor fijo.
  2.3 El valor inicial nunca cambia

3.  Se puede usar tanto interpolación {{}}, como property binding [], pero se está planteando priorizar a property binding, ya que parece mas claro.

<p><img src="{{heroImageUrl}}"> is the <i>interpolated</i> image.</p>
<p><img [src]="heroImageUrl"> is the <i>property bound</i> image.</p>

<p><span>"{{title}}" is the <i>interpolated</i> title.</span></p>
<p>"<span [innerHTML]="title"></span>" is the <i>property bound</i> title.</p>
La interpolación (de atributos) es traducida a las correspondientes propiedades del DOM antes de renderizar la vista.

4. Seguridad: Tanto la interpolación como el property binding, rechazan ejecutar el código <script> escondido en strings. Así:

  evilTitle = 'Template <script>alert("evil never sleeps")</script>Syntax';
<p><span>"{{evilTitle}}" is the <i>interpolated</i> evil title.</span></p>

daría: 

"Template <script>alert("evil never sleeps")</script>Syntax" is the interpolated evil title

mientras:

<p>"<span [innerHTML]="evilTitle"></span>" is the <i>property bound</i> evil title.</p>
daría: 


"Template Syntax" is the property bound evil title






martes, 20 de diciembre de 2016

Angular 2. Formularios. Entrega 1

Voy a seguir esta vez la guia oficial de Angular 2.


1. Creación del proyecto con angular-cli.

Vamos a crear un proyecto llamado angular-forms. Para ello lo haremos tal como dijimos en un post anterior. Hay que tener en cuenta que en entornos windows no hay que indicar el "sudo", pero hay que ejecutar el comando en interprete de comandos en modo administrador.

sudo ng new angular-forms

(lo de sudo es si utilizamos Ubuntu) y esperamos lo nuestro.

y nos situamos en la carpeta angular-forms y ejecutamos

sudo ng serve 

para ver si funciona (abrimos el navegador en http://localhost:4200 y debe aparecer el texto "app works!" en el navegador).


2. Construcción de un formulario con un componente y template (plantilla).

Hay que tener cuidadado si utilizamos angular.cli para hacer este ejemplo ya que:

1. A veces no refresca el contenido bien o lo bastante rápido. A veces parece que se ha refrescado pero, no ha compilado bien. En este caso, sucede que no reconoce la función newHero(). Tras meterle dentro codigo adicional como :
  "console.log("lo que sea"); 
ha funcionado correctamente.


2.Las rutas que se proponen en el ejemplo, deben de ser relativas al elemento que se indica (tienen que ser del tipo "./". Por ejemplo si copiamos el componente hero-form.component.ts del ejemplo, observamos esta línea:
   templateUrl:'hero-form.component.html'
Pues, debería ser esta otra            
  templateUrl:'./hero-form.component.html'

3. La distribución de los componentes puede no ser la misma. Por ejemplo el fichero index.html, angular cli lo mete en la carpeta padre de la carpeta app, y no en la misma como se muestra en el tutorial de angular.

4. Para acceder al CSS que nos proporciona Bootstrap (no confundir con el comando Bootstrap que aparece dentro de los componentes), se puede meter en la sección "head" del index.html


<link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.7/css/bootstrap.min.css" integrity="sha384-BVYiiSIFeK1dGmJRAkycuHAHRg32OmUcww7on3RYdg4Va+PmSTsz/K68vbdEjh4u" crossorigin="anonymous">

5. El fichero de estilos forms.css debera estar en el mismo directorio que index.html o sea en src, pero para acceder a él será tambien con ruta relativa "./" y no en forma absoluta, si no no lo encuentra.

<link rel="stylesheet" href="./forms.css">

Dicho esto, ya funciona el ejemplo de la documentación de angular.

Vamos a destacar lo más importante:

1. Se crea un clase Hero que se guarda en hero.ts (dentro de la carpeta app), que define la información a guardar.
-----------------------------------------------------------------------------------------------------------------


2. Se crea el componente HeroFormComponent que se guarda en hero-form.component.ts (dentro de la carpeta app), que:

  2.1 hay que indicarle la ruta RELATIVA al componente html:
       templateUrl:'./hero-form.component.html'

2.2 No admite la sentencia:
       moduleId: module.id,

2.3 Se definen estas funciones, que seran llamadas en los botones del form:
        onSubmit() { this.submitted = true; }
newHero() {this.model = new Hero(42, '', ''); }
 
  2.4 Inicialmente se definió el "getter" diagnostic para tener indicadores.
        get diagnostic() { return JSON.stringify(this.model); }
  2.5 Se tiene que importar la clase Hero
        import { Hero } from './hero'

-----------------------------------------------------------------------------------------------------------------

3. Se crea la plantilla hero.form.components.html que es capaz de recoger información del componente:
  3.1 Para mostrar información fuera de una etiqueta html se utiliza la notación moustache:
        {{diagnostic}}
3.2 Dentro del la etiqueta html hay que utilizar [ngModel] para vincular a un atributo del componente.
  3.3 Tambiém se pueden definir eventos dentro de la etiquete entre paréntesis (ngModelChange) .Para el caso de darle a un botón se le puede asignar varias funciones (Ojo, si hacemos reset al form, se borrará toda la información del form. Por tanto yo invertiría el orden de las funciones a heroForm.reset(); newHero() en vez de lo que se pone a continuación que es copia del tutorial de angular.
<button type="button" class="btn btn-default" (click)="newHero(); heroForm.reset()">New Hero</button>
  3.4 Existen clases que indican que:
       3.4.1: Control visitado o no  (ng-touched, ng-untouched)
       3.4.2: Control con valor cambiado o no (ng-dirty, ng-pristine)
       3.4.3: Contor con valor correcto o ni (ng-valid, ng-invalid)
   3.5 Para hacer referencia a un componente se define dentro de el com #nombre_componente y después de puede acceder a el con notación mustache. En este caso se le ha dado el nombre spy
<input type="text" class="form-control" id="name"
  required
  [(ngModel)]="model.name" name="name"
  #spy >
<br>TODO: remove this: {{spy.className}}
    y devuelve "TODO: remove this: form-control ng-untouched ng-pristine" que contine la información de las clases css del control texto.

  3.6 Se les ha dado el atributo required para la validación en algunos campos.
  3.7 Como ya hemos dicho en el punto 3.3, la función reset() del formulario borra el contenido de los controles, por tanto si ejecutamos newHero() despues del reset, los controles se llenaran con la información proporcionada por defecto en este constructor. Si se ejecuta anteriormente, se perderán los valores por omisión, y el formulario se quedará en blanco.
  3.8 Se pueden asociar atributos de las tags de html a variables del componente. Por ejemplo el atributo hidden de la etiqueta div, que toma el valor de la variable submitted
<div  [hidden]="submitted"> 
 3.9 La variable o propiedad valid del form se puede asignar también al atributo disabled de un control:
<button type="submit" class="btn btn-default" [disabled]="!heroForm.form.valid">Submit</button>

3.10 Con *ngFor rellenamos el control select
  <select class="form-control" id="power" required>
      <option *ngFor="let pow of powers" [value]="pow">{{pow}}</option>
  </select>


-----------------------------------------------------------------------------------------------------------------

4. El módulo app.module.ts debe de hacer referncias a HeroComponent tanto en import como en declarations (y como estamos utilizando forms, también debe importar FormsModule)
    ......
import { FormsModule } from '@angular/forms';
import { AppComponent } from './app.component';
import { HeroFormComponent } from './hero-form.component';

 @NgModule({
....
declarations: [ AppComponent, HeroFormComponent ],
....
})


Al final queda