sábado, 26 de agosto de 2017

Content Projection

En la entrada de hoy vamos a ver que es y como se usa Content Projection en Angular.

Angular tiene un tag que se llama <ng-content>

Este tag puede servir por ejemplo para crear contenido dinámico dependiendo del componente que queramos. Sería similar a un "placeholder" que en algún momento dado tendrá contenido.

Vamos a entenderlo más fácilmente con un ejemplo.

Tenemos un componente muy sencillo (header.component) que únicamente muestra un título.
El código sería algo así como:

import { Component } from '@angular/core';

@Component({
  selector: 'app-header',
  templateUrl: './header.component.html'
})
export class HeaderComponent  {
  title = 'Título del componente';
}

Y en el template únicamente interpolamos el título a mostrar:

<h2>{{title}}</h2>

Usamos este componente en el componente principal (app.component.ts)


Ahora bien, imaginemos que ese componente lo queremos usar en varios sitios de nuestra aplicación o incluso en varios sitios dentro de nuestro componente principal, pero cambiando el contenido (título a mostrar) dentro de cada uno de ellos.

Para hacer esto, podemos hacer proyección de contenido, colocando contenido dinámico.

En primer lugar dentro de nuestro componente header vamos a reemplazar el título interpolado por el tag <ng-content> que nos va a servir para hacer content-projection


Ahora, a la hora de usar el componente header, podemos proyectar el contenido de la siguiente manera:



Y el resultado quedaría algo así:


Ahora imaginemos que además de mostrar el título de manera dinámica queremos mostrar también un subtítulo.
Para eso añadimos otro ng-content dentro del componente header.
Cuando tenemos más de un "placeholder" dentro de un componente se conoce con el nombre de slots

Para identificar lo que queremos colocar en cada slot, podemos usar "selectores" de manera similar a como hacemos con jQuery

Par ello lo indicamos con el atributo "select" de esta forma:



En el ejemplo anterior estamos diciéndole al componente que en el slot1 coloque el contenido que indiquemos en el h2, y que en el slot2 muestre el que indiquemos en el h4. Podriamos usar otros selectores de manera similar a como se hace en jQuery (# para el selector por Id , . para el selector de clase css, etc.)

Únicamente nos queda "proyectar" el contenido, es decir, nuestro título y subtítulo:



El resultado se vería de esta forma...



Como hemos visto es muy fácil usar proyección de contenido y puede resultarnos muy útil en determinadas ocasiones en las que queramos mostrar contenido dinámico dentro de nuestros componentes.

Hasta la próxima entrada!!

jueves, 17 de agosto de 2017

Usando Angular Material en nuestros proyectos (Parte I)

En la entrada de hoy vamos a ver como podemos usar Angular Material en nuestras aplicaciones Angular, utilizando como siempre Angular-cli

En primer lugar crearemos un nuevo proyecto con estilos sass, ya que nos va a dar más juego que el css de toda la vida.

Para ello, hacemos uso del flag --style=sass. Es decir, desde nuestra terminal crearemos el proyecto mediante la sentencia:

$ ng new ng4-material --style=sass

Una  vez creado el proyecto (y una vez que hemos accedido al directorio del mismo), instalaremos Angular Material mediante npm:

npm install --save @angular/material @angular/cdk

tal como se indica en la guía -https://github.com/angular/material2/blob/master/guides/getting-started.md-, tenemos que instalar también las @angular/animations, luego procedemos a instalarlas

$ npm install --save @angular/animations

Una vez que tenemos instalados los paquetes anteriores en nuestro proyecto, tenemos que importar el módulo MaterialModule

Para ello en el app.component.ts importamos el módulo y lo declaramos en la sección "imports" tal como se muestra a continuación:



Una vez importado el módulo no hace falta hacer nada más para empezar a utilizar los componentes UI de Material.

Vamos a colocar algún componente de UI material en nuestra template html para comprobarlo:




El resultado es el siguiente.


Como podemos observar, por un lado el botón circular contiene un icono de Material Design que no se está visualizando, y por otro los botones tienen un aspecto "pobre".
Para poder hacer uso de los iconos de Material tenemos que cargar los iconos en el index.html:

<link href="https://fonts.googleapis.com/icon?family=Material+Icons" rel="stylesheet">

Para poder usar los colores de los botones, tenemos que usar un theme de los que trae por defecto Material (o bien podemos crear una personalizado).
 Angular Material viene con varios themes predefinidos que se pueden consultar en la carpeta node_modules/@angular/material/prebuilt-themes de nuestro proyecto.

Para este ejemplo vamos a usar el theme indigo-pink 
en el fichero styless.sass, vamos a importar el fichero indigo-pink.css, para ello hacemos:
@import '~@angular/material/prebuilt-themes/indigo-pink.css';

El resultado es el siguiente:


Antes de terminar (continuaremos viendo más componentes de Material en entradas posteriores) una anotación:

Algunos componentes de angular material dependen del módulo de animación para poder realizar transiciones más avanzadas. Si queremos que estas animaciones funcionen en la aplicación,
tenemos que instalar el módulo @angular/animations e incluir el BrowserAnimationsModule en la aplicación.

Lo dicho, en entradas posteriores veremos el tema de las animaciones y algunos de los componentes que podemos usar con Angular Material



miércoles, 9 de agosto de 2017

Multiples peticiones http con rxjs: ForkJoin

Un escenario común que nos solemos encontrar al realizar cualquier aplicación es la de obtener una serie de datos desde una API y mostrar esa información al usuario.

Obtener múltiples solicitudes asíncronas y gestionarlas puede ser complicado, pero con el servicio Http de Angular y un poco de ayuda de la biblioteca RxJS se puede lograr en sólo unas pocas líneas de código.

Vamos a verlo con un ejemplo.
En nuestro ejemplo vamos a obtener la información de un Pokemon a partir de u Id. Este Id vamos a suponer que lo recibimos como parámetro (en el ejemplo por simplificar realmente no lo recibimos como parámetro sino que le asignamos un Id cualquiera)

En primer lugar crearemos el servicio PokemonService (pokemon.service.ts) que contiene los dos métodos que vamos a utilizar. Uno para obtener la información del pokemon, y la otra para obtener su "pokemon-form"




Lo que haríamos "normalmente" es llamar al método getPokemonInfo, y una vez obtenidos los datos, llamar al método getPokemonForm. Algo parecido a esto:

this.pokemonService.getPokemonInfo(this.pokemonId).subscribe(data => {
this.pokemonService.getPokemonForm(this.pokemonId)
.subscribe(res => console.log(res));
})

Esto por un lado tiene la desventaja de que empezamos a tener estructuras anidadas de Observables, lo cual no es muy legible, y más si en vez de dos solicitudes tenemos tres, cuatro, etc.
Y por otro lado, las dos solicitudes eran secuenciales, luego primero tenemos que realizar una llamada, y únicamente cuando esta llamada se haya realizado satisfactoriamente hacer la segunda llamada, cuando para nuestro ejemplo esto realmente no es necesario.

Aunque habría otras opciones, en este ejemplo vamos a ver como realizar esto mismo con el  operador llamado forkJoin.

Si está familiarizado con Promises esto es muy similar a Promise.all (). El operador forkJoin () nos permite tomar una lista de Observables y ejecutarlos en paralelo. Una vez que cada Observable de la lista devuelve un valor, el forkJoin emite un único valor Observable que contiene una lista de todos los valores resueltos de los Observables de la lista.

Nuestra lista de observables será:

    const pokemonInfo = this.pokemonService.getPokemonInfo(this.pokemonId);
    const pokemonForm = this.pokemonService.getPokemonForm(this.pokemonId);

y para usar el operador forkJoin  hacemos:

Observable.forkJoin([pokemonInfo, pokemonForm])
.subscribe(results => {
this.pokemon.nombre = results[0].name;
this.pokemon.altura = results[0].height;
this.pokemon.peso = results[0].weight;
this.pokemon.imagen = results[1].sprites.front_default;
})

Como se puede observar, el operador forkJoin recibe una lista de Observables (pokemonInfo y pokemonForm) y devuelve los resultados (results[0] y results[1]).
Una vez obtenidos los resultados asignamos las propiedades que deseamos al objeto pokemon.

El código completo de esta parte sería:


Puedes ver el código completo en:
https://stackblitz.com/edit/angular-mynqug


martes, 8 de agosto de 2017

Validacion condicional en Reactive forms

En la entrada de hoy vamos a ver como realizar validación condicional en Angular2. Para ello vamos a crear un formulario reactivo (reactive form) donde solicitaremos un medio de pago (contrareembolso, transferencia y tarjeta).
En el caso de que el medio de pago sea "tarjeta", marcaremos los campos "número de tarjeta" y "fecha de caducidad" como requeridas.

Vamos al código:

Lo primero que debemos hacer es importar en el app.module.ts el módulo ReactiveFormsModule (que se encuentra dentro de @angular/forms) ya que vamos a usar los formularios reactivos.

Ahora vamos a crear el formulario:

(app.component.ts)

import { Component, OnInit } from '@angular/core';
import { FormGroup, FormBuilder, Validators, FormControl } from '@angular/forms';

@Component({
selector: 'app-root',
templateUrl: './app.component.html',
styleUrls: ['./app.component.css']
})
export class AppComponent implements OnInit {
private form: FormGroup;
constructor(private fb: FormBuilder) {
this.form = this.fb.group({
'FormaPago': ['Contrareembolso', Validators.required],
'NumeroTarjeta': [null],
'FechaCaducidad': [null]
});
}

ngOnInit(): void {
this.form.controls['FormaPago'].valueChanges
.subscribe(value => {
if (value === 'Tarjeta') {
this.setValidatorRequired('NumeroTarjeta', Validators.required);
this.setValidatorRequired('FechaCaducidad', Validators.required);
} else {
this.setValidatorRequired('NumeroTarjeta', null);
this.setValidatorRequired('FechaCaducidad', null);
}
});
}

setValidatorRequired(campo, requerido) {
this.form.controls[campo].setValidators(requerido);
this.form.controls[campo].updateValueAndValidity();
}

}

En el código anterior hemos declarado nuestro formulario (form) el cual tendrá tres campos:
FormaPago, cuyo valor predeterminado es 'Contrareembolso' y es requerido.
NumeroTarjeta y FechaCaducidad, en principio no son requeridos (lo serán cuando el método de pago seleccionado sea "Tarjeta")

En el metodo ngOnInit chequeamos los cambios que ocurran en el control "FormaPago". Cuando la forma de pago seleccionada sea "Tarjeta", establecemos para ese campo el validador "Required" (Validators.required) y actualizamos el estado del mismo mediante la instrucción updateValueAndValidity

Si el método de pago es distinto de "Tarjeta" quitamos la validación requerida (o lo que es lo mismo, la establecemos a null) y al igual que en el caso anterior llamamos al método updateValueAndValidity

Respecto al html (no se ha tenido en cuenta el aspecto visual) poco que comentar.
Hay unos controles radioButton para la selección de la forma de pago, los controles para indicar el número de tarjeta y fecha de caducidad (por simplicidad no se controla ni la longitud ni formato de estos campos) y un botón de enviar que estará disponible si se indican los campos requeridos.
Aquí les dejo el html del mismo:

<form>
<div>
<h3>Forma de pago</h3>
<input type="radio" value="Contrareembolso" name="formapago"
[formControl]="form.controls['FormaPago']"> Contrareembolso
<input type="radio" value="Trasferencia" name="formapago"
[formControl]="form.controls['FormaPago']"> Trasferencia
<input type="radio" value="Tarjeta" name="formapago"
[formControl]="form.controls['FormaPago']"> Tarjeta
</div>
<div *ngIf="form.controls['FormaPago'].value==='Tarjeta'">
<h3>Datos de la tarjeta</h3>
<span>Número de tarjeta</span>
<input type="text" [formControl]="form.controls['NumeroTarjeta']">
<br>
<span>Fecha caducidad (mes/año)</span>
<input type="text" [formControl]="form.controls['FechaCaducidad']">
</div>
<div>
<button type="submit" [disabled]="!form.valid">Enviar</button>
</div>
</form>


El código fuente esta disponible en:

https://gist.github.com/jorgetrigueros/328c569da073de5655aa66b194f91fb5#file-validacioncondicional-ts