jueves, 19 de abril de 2018

Resolvers en Angular

Un resolver es un código que se ejecuta después de hacer clic en un enlace, y antes de que se haya cargado el componente asociado a dicho enlace.

Básicamente el flujo que realizamos al usar un resolver es:

- Hacemos clic en un enlace que habremos definido en un router module.
- Se ejecuta el código asociado al resolver. El resolver puede devolver un valor (que puede ser un observable o cualquier otro tipo) al componente asociado al enlace sobre el que hemos hecho clic.
- Cargamos el componente pudiendo usar los datos obtenidos del resolver.

Vamos a ver un ejemplo donde mostraremos una serie de enlaces. Cada enlace va a representar un usuario. Al hacer clic en un usuario, pasaremos en la ruta el id asociado al mismo, y antes de cargar el componente asociado, haciendo uso de un resolver vamos a hacer una petición get a un api para traernos la información del usuario, de tal manera que cuando la página esté cargada la información se va a encontrar disponible.

En primer lugar vamos a crear un servicio que implemente la interface genérica resolve<T>.
Esta interface tiene el método resolver() que tenemos que sobreescribir.

En nuestro ejemplo, el método va a recibir un párametro de tipo ActivatedRouteSnapshot que vamos a usar para recuperar los parámetros de la ruta (vamos a tener rutas del estilo users/1, users/2, etc.)

const userId = route.params['id'];

Una vez que obtenemos el id del usuario, llamaremos al api de jsonplaceholder estableciendo una url del siguiente estilo https://jsonplaceholder.typicode.com/users/1




El componente donde vamos a mostrar la información del usuario se llamará user.component.ts
Esta información la va a recibir el componente una vez que el usuario haga clic en el enlace del menú asociado al mismo, y será entoces cuando se llame al resolver que se encargará de recibir el parametro del usuario y realizar la petición httpGet como veremos más adelante.

En el OnInit del componente lo que hacemos es recuperar la información de la llamada que hemos realizado al Api de jsonplaceholder. Esta información la tenemos en el objeto de la clase ActivatedRoute que inyectamos en el contructor, en concreto dentro de la propiedad "data".



En las rutas de nuestra aplicación, que las vamos a definir en el módulo principal será donde indiquemos los resolver que queremos asignar a cada una de ellas. Al inicio de la entrada comentamos que "un resolver es un código que se ejecuta después de hacer click en un enlace, y antes de que se haya cargado el componente asociado a dicho enlace"... y el objetivo que queremos es que cuando hagamos clic en el usuario-1 se cargue la información del usuario-2, con el usuario 2 la información del 2, etc.  por lo que las rutas asociada al componente UserComponent (users/:id) vamos a resolverlas con el UserResolverService

const ROUTES: Routes = [
  { path: '', component: AppComponent },
  { path: 'home', component: AppComponent },
  {
    path: 'users/:id',
    component: UserComponent,
    resolve: {
      user: UserResolverService
    }

  }
];


 Como podemos observar  en el código anterior, las rutas de "users" se resuelven con el servicio que hemos creado anteriormente, el que se encarga de realizar una petición http y dejar los resutados en "user"

En la siguiente imagen vemos el resultado que se muestra en el navegador:


A modo de resumen, hemos creado un servicio que implementa la interfece Resolve, sobreescribiendo el método resolve() de la misma.
Este método se encarga de obtener el parámetro de la ruta sobre la que hemos hecho clic, y devuelve un observable resultado de una petición http al api especificado.
Hemos definido las rutas, especificando para las rutas de usuario un resolve que está asociado al servicio creado inicialmente, y finalmente en el componente hemos pintado en el template los datos que hemos recuperado antes de que se cargara el mismo.

El código fuente completo de este ejemplo se encuentra en
https://github.com/jorgetrigueros/angular-resolver-demo


martes, 17 de abril de 2018

Cómo desplegar una aplicación Angular en GitHub

Normalmente estamos acostumbrados a usar GitHub como repositorio de código fuente de nuestras aplicaciones ya sean angular o de cualquier otro tipo, y no necesarimente solo aplicaciones.

En la entrada de hoy vamos a ver cómo podemos desplegar una aplicación Angular en GitHub.

En primer lugar crearemos lógicamente la aplicación angular que llamaremos "demo-deploy-angular-app-github"
La aplicación que vamos a desplegar es una aplicación angular con un componente que  muestre un simple "Hola mundo". Lo que realmente nos interesa en la entrada de hoy es ver como podemos desplegar una aplicación y no tanto el contenido o la complejidad de la misma.

Posteriormente necesitamos crear el repositorio en GitHub. En mi caso he creado un repositorio con el mismo nombre de la aplicación, es decir, "demo-deploy-angular-app-github"

Una vez que el repositorio ha sido creado vinculamos nuestro proyecto a GitHub, como en cualquier otro proyecto:

> git remote add origin https://github.com/jorgetrigueros/demo-deploy-angular-app-github.git
 
 Para desplegar una aplicación Angular en GitHub necesitamos instalar angular-cli-ghpages de manera global, por lo que ejecutaramos en nuestra consola la siguiente instrucción:

> npm install -g angular-cli-ghpages

  Una vez que hemos instalado la librería tenemos que construir el proyecto, es decir, tenemos que ejecutar desde el angular-cli la siguiente instrucción:

> ng build --prod --base-href "https://jorgetrigueros.github.io/demo-deploy-angular-app-github/"

El flag --pro indica que queremos hacer una construcción para producción. El flag --base-href  modifica la etiqueta base (<base href="/">) del index.html con la url establecida.

Después de haberse ejecutado el comando, se nos debería haber creado una carpeta "dist" con una serie de ficheros bundle.js como se ve en la siguiente imagen:

Una vez que hemos creado el proyecto, fnalmente nos queda publicarlo, y esto podemos realizarlo con el siguiente comando de la librería angular-cli-ghpages:

> angular-cli-ghpages --no-silent

Podemos ver las opciones disponibles para ejecutar ese comando en la página web de la librería:

https://www.npmjs.com/package/angular-cli-ghpages

Si todo ha ido bien veremos un mensaje de despliegue correcto, y podemos verificarlo si accedemos a nuestra dirección https://<username>.github.io/<reponame>

En nuestro ejemplo podemos ver el resultado en la dirección https://jorgetrigueros.github.io/demo-deploy-angular-app-github












domingo, 8 de abril de 2018

ngx-model: Libraría para la gestión del estado en Angular

A la hora de pensar como gestionar el estado en nuestras aplicaciones Angular lo lógico es pensar en utilizar una librería como ngrx.

El problema de ngrx es que supone un cambio importante en el paradigma de programación tal como estamos acostumbrados. Sin embargo, resulta indudable que tener separado el estado de la aplicación como una única fuente de verdad absoluta a la que todos consultan y donde todos modifican tiene grandes ventajas.

Si queremos gestionar el estado de nuestras aplicaciones Angular sin tener que recurrir a la complejidad de la librería ngrx, podemos usar Angular Model Pattern, una librería en la que en palabras de su autor sirve para Gestión de estados sencilla con una API minimalista, flujo de datos unidireccional, compatibilidad con múltiples modelos y datos inmutables expuestos como RxJS Observable

Para usar esta librería tenemos dos opciones:

1. instalando la dependencia ngx-model: npm install ngx-model --save
2. Creando nosotros el servicio tal como se indica en la página de la librería, en el apartado  "Use as Pattern" (https://tomastrajan.github.io/angular-model-pattern-example#/getting-started)

En cualquiera de los dos casos lo que la librería hace es, una vez que especificamos los tipo de datos con el que vamos a trabajar, -haciendo uso de tipos genéricos- usar un tipo "especial" de Observable (BehaviorSubject), para proporcionar métodos para la consulta y modificación de los datos del modelo, y otros métodos como el clonado de objetos manteniendo la inmutabilidad de los mismos.

Para hacer uso de la lbrería tenemos que:

- Importar ModelFactory en nuestro servicio, inyectándolo como dependencia en el constructor y crear una instancia de modelo.


      // inject model factory with specified type
      constructor(private modelFactory: ModelFactory<Todo[]>)
 

En los componentes o servicios que se quiera consultar/modificar el estado tendriamos que inyectar el servicio que hemos creado y hacer uso de los métodos del mismo que son los que gestionan el estado de nuestro modelo.

Vamos a verlo en un ejemplo que nos va a permitir guardar, actualizar y eliminar datos como nombre, apellidos y fecha de nacimiento de nuestros contactos que queramos almacenar.

Como queremos gestionar el estado de nuestros contactos vamos a crear una interfaz que represente este modelo.

export interface Contacto {
id: string;
nombre: string;
apellidos: string;
fechaNacimiento: string;
}

Vamos a crear un servicio de modelo donde utilizar esta interfaz para instanciar el modelo de nuestras contactos e implementar los métodos que cambian y consultan los datos del modelo, es decir, métodos para inserción, actualizacióny borrado de elementos.

El código de nuestro servicio es el siguiente:

@Injectable()
export class ContactoService {
private model: Model<Contacto[]>;
contacto$: Observable<Contacto[]>;

constructor(private modelFactory: ModelFactory<Contacto[]>) {
// create model and pass initial data
this.model = this.modelFactory.create([]); 
// expose model data as named public property
this.contacto$ = this.model.data$;
}

.........
}


En nuestro servicio hemos creado el modelo como un array de contactos a través del modelFactory y asignado a un Observable del mismo tipo.

El servicio tiene dos metodos. Uno para crear "contactos" que lo que hace es a través de la función set de la clase establecer el valor del "nuevo" modelo, que será lo que ya teniamos concatenando el nuevo contacto:

addContacto(nombre: string, apellidos: string,  
fechaNacimiento: Date) {
const id = generateUid('-');
const contactos = this.model.get();
this.model.set(contactos.concat([{ id, nombre, apellidos,  
fechaNacimiento }]));
}

y otro método para eliminar un contacto existente por medio de su Id, (que habremos generado a traves del método generateUid tal como puede observarse en el código anterior)

removeContacto(id) {
const contactos = this.model.get();
const index = contactos.findIndex(x => x.id === id);
if (index !== -1) {
this.model.set([...contactos.slice(0, index), 
 ...contactos.slice(index + 1, contactos.length)]);
}
}

Para el método de eliminar, obtenemos los contactos usando el método get() del modelo (model.get()) para posteriormente buscar el indice del elemento cuyo id es el especificado.
Si lo encontramos, establecemos el nuevo modelo dejando "fuera" el elemento encontrado, es decir, eliminando en otras palabras. Esto lo hacemos usando la propedad de inmutabilidad por medio del spread operator de ES6 para no tener que modificar el array inicial.

Por otra parte vamos a crear tres componentes, uno para crear nuestros contactos, otro para listarlos y un tercero que será el detalle del ccontacto en sí mismo.

El componente que crea los contactos (ContactoNuevoComponent) tendrá el siguiente layout:

<h1 class="title">
Añadir un contacto</h1>
<div class="field">
<label class="label">Nombre</label>
<div class="control">
<input class="input" type="text" placeholder="Nombre" #nombre>
</div>
</div>
<div class="field">
<label class="label">Apellidos</label>
<div class="control">
<input class="input" type="text" placeholder="Apellidos" 
#apellidos>
</div>
</div>
<div class="field">
<label class="label">Fecha nacimiento</label>
<div class="control">
<input class="input" type="text" 
 placeholder="Fecha nacimiento" #fechanacimiento>
</div>
</div>
<div class="field is-grouped">
<div class="control">
<button class="button is-link" 
(click)="addContacto(nombre.value, apellidos.value, 
fechanacimiento.value)">Añadir contacto</button>
</div>
</div>

en cuanto al código, unicamente tendremos el método addContacto que recibe como parámetros el nombre, apellidos y fecha de nacimiento del contacto que queremos crear:

addContacto(nombre: string, apellidos: string
 fechaNacimiento: Date): void {
this._contacto.addContacto(nombre, apellidos, fechaNacimiento);
}

En este método llamamos al método addContacto del servicio _contacto (ContactoService) que tendremos que inyectar en el constructor

El componente que nos va a mostrar nuestros contactos (ContactoListComponent) no tiene mucha historia. Vamos a cargar los contactos de nuestro modelo haciendo uso del servicio de contactos y a pasar el propio contacto al componente detalle que recibe como parametro Input() el contacto.

<div class="card" *ngFor="let contacto of _contacto.contacto$ | async;">
<app-contacto-detail [contacto]="contacto"></app-contacto-detail>
</div>


el componente que muestra el detalle (ContactoDetailComponent) recibe como parametro Input() el propio contacto y lo muestra en el template:
export class ContactoDetailComponent implements OnInit {
@Input() contacto: Contacto;
constructor(public _contacto: ContactoService) { }

<div class="card-content">
<div class="content">
<p class="card-header-title">
<span class="title">{{contacto.nombre}} {{contacto.apellidos}}</span>
</p>
<span class="subtitle">{{contacto.fechaNacimiento}}</span>
</div>
</div>
<footer class="card-footer">
<a href="#" class="card-footer-item" (click)="eliminarContacto(contacto.id)">Eliminar</a>
</footer>


y el método para eliminar el contacto llamando al método removeContacto del servicio:

/**
* Eliminar un contacto
* @param id
*/
eliminarContacto(id) {
this._contacto.removeContacto(id);
}


El resultado se verá similar al de la siguiente imagen:



En esta entrada hemos visto como podemos gestionar el estado en angular haciendo uso de la librería ngx-model, que si bien no tiene toda la potencia que nos ofrece las librerías de redux para angular, tampoco tiene la misma complejidad y puede sernos útil en multiples situaciones.

El código fuente se encuentra disponible en https://github.com/jorgetrigueros/ngx-model