viernes, 25 de marzo de 2016

Intro Angular 2 con TypeScript

Angular 2 está al caer así que hace días que quería echar un vistazo a ver que tal. He desarrollado una aplicación que comunica con una api personal (la estoy desarollando en asp.net core) que permite gestionar tu propio álbum de fotos. A continuación comparto algunas notas por si alguien se anima a empezar con la parte cliente, aunque no tengas api puedes simular lo que hago con un mock .json. Antes que sigas leyendo comentar que todo lo que explico en este post ya está explicado en la doc oficial de Angular o en otros blogs. En este post lo que hago es ir explicando paso a paso completando con enlaces que me parecen interesantes y comparto algún que otro repo e información que puede ayudar a otros a iniciarse, se podría decir que comparto mis apuntes y pruebas de concepto que puedes seguir si estás interesado.

En primer lugar mencionar TypeScript que es el lenguaje recomendado para desarrollar con Angular 2, esto no quiere decir que estés obligado a usarlo aunque es recomendable. Personalmente, visto que SPA ha venido para quedarse me gusta la idea de tener código tipado en los desarrollos front y poder beneficiarme de las ventajas de un lenguaje OOP. Otra ventaja es que IDE's que interpreten TypeScript te ofrecerán intelisense y te avisarán en tiempo de diseño sobre el código que puede dar problemas al compilar o en runtime sin necesidad de tener que ejecutar la aplicación.

Volviendo a Angular 2. Esto va de componentes, tenemos que pensar que cada elemento con lógica de la vista puede ser un componente y estos son jerárquicos. Si vienes de Angular 1.x tienes ganado el concepto SPA y que ya se usaban directivas en la versión anterior. Ya verás que en Angular 2, no hay demasiada diferencia entre un componente y una directiva.

Configuración Entorno


Yo he optado por empezar con Visual Studio Code, para configurar el entorno tenemos que seguir los pasos de este tutorial de Angular.io. Angular 5 min Quickstart ts e instalarte Node y Git.

Es recomendable leer estos tutoriales y entender lo básico sobre como crear el boostrapper, el primer componente, navegación, como funciona la compilación (o transpilación) typescript, npm, etc.

Una vez tengas esto por la mano te recomiendo usar una aplicación de seed o referencia en la que fijarte para aprender, en mi caso, me clone este repo de Github via @danwahlin que me ha ido perfecto para tener un entorno estable ya configurado y tener ejemplos en los que apoyarme.

Estructura de un "objeto" en Angular


Todo componente, directiva o servicio en Angular 2 se estructura del siguiente modo:
  • Bloque Import
    El simil en otros lenguajes vendrían a ser los using, includes, scripts en función del lenguaje y es una referencia a donde están ubicados los componentes, servicios o directivas que usaremos en el "objeto" que estamos definiendo
  • Definición de mi "objeto"
    Este bloque variará en función de si estamos creando un componente, servicio o directiva. Para crear un servicio usaremos @Injectable, un componente @Component o una directiva @Directive.
  • Implementación del objeto (export)
    Clase con la implementación de tu objeto.

Servicio de acceso a datos


Ahora veremos como crear un servicio que comunique con la Api, si has descargado el repo que te comentaba de @danwalhim estoy calcando su DataService. Vereis que en Angular 2 se usan Reactive Extensions, os dejo un buen post de @thoughtram donde podéis leer más al respecto.
import { Injectable } from 'angular2/core';
import { Http, Response } from 'angular2/http';
import {Observable} from 'rxjs/Observable';
import 'rxjs/add/operator/map'; 
import 'rxjs/add/operator/catch';

@Injectable()
export class DataService {
  
    baseUrl: string = '';

    constructor(private http: Http) { }

    getAlbums(){
      return this.http.get(this.baseUrl + 'albums.json')
                      .map((res: Response) => res.json())
                      .catch(this.handleError);               
    }
    
    handleError(error: any) {
        console.error(error);
        return Observable.throw(error.json().error || 'Server error');
    }

}
Como vemos la definición del servicio no implica definir un bloque @component sino que en este caso simplemente indicamos que será un elemento @injectable.

Componente Lista Álbumes


Ahora crearemos un componente que se encargue de mostrar una lista de álbumes usando el servicio que acabamos de crear.
import { Component } from 'angular2/core';
import { DataService } from '../shared/services/data.service';

@Component({ 
  selector: 'albums', 
  providers: [DataService],
  templateUrl: 'app/albums/albums.component.html'
})

export class AlbumsComponent {

  title: string;
  albums: any[] = [];

  constructor(private dataService: DataService) { }
  
  ngOnInit() {
    this.title = 'Albums';

    this.dataService.getAlbums()
        .subscribe((albums:any[]) => {
          this.albums = albums;
        });
  }
}
En la definición de @Component indicamos un selector, en providers indicamos los servicios que inyectamos vía constructor, le especificamos la ruta donde tendremos la vista y de momento no necesitamos nada más. En la implementación tenemos un array con los albumes y al iniciarse el componente los obtenemos usando el servicio que hemos creado en el paso anterior.

Vista Lista Álbumes


Pasamos a crear una vista minimalista que nos muestre los datos que hemos obtenido del servicio. Ya lo iremos complicando paso a paso explicando y entendiendo lo que hacemos. Al menos a mi es como me gusta aprender un nuevo framework.
<div class="row">
    <div>
        <div class="col-xs-6">Title</div>
        <div class="col-xs-6">Description</div>
     </div>
    <div *ngFor="#album of albums">
        <div class="col-xs-6">{{album.title}}</div>
        <div class="col-xs-6">{{album.description}}</div>
     </div>
<div>
Esto debería renderizarse con los datos que tengas en el servicio o mock. En los datos que devuelve mi api se puede apreciar un gran factor de postureo... no se me ocurrían otros álbumes jajajjajajaj.



Interacción entre vista y componente


Ya para acabar el post de introducción a Angular 2 veremos algunos modos de interactuar entre vista y componente. Añadiremos un campo de texto que nos sirva para filtrar el contenido que ha devuelto servidor, primero lo haremos en plan básico y en segundo lugar lo haremos creando un componente para el filtro.

Usando el mismo componente:.
<div><input #term type="text" (keyup)="filter(term.value)"></div>
El siguiente paso será añadir un método filter al componente y la propiedad filteredAlbums. Vuelvo a mostrar como quedaría el componente.
export class AlbumsComponent {

  title: string;
  albums: any[] = [];
  filteredAlbums: any[] = [];

  constructor(private dataService: DataService) { }
  
  ngOnInit() {
    this.title = 'Albums';

    this.dataService.getAlbums()
        .subscribe((albums:any[]) => {
          this.albums = this.filteredAlbums = albums;
        });
  }
  
  filter(term: string){
      this.filteredAlbums = [];
      this.albums.forEach(function(album){
          if (album.title.indexOf(term) > -1 || album.description.indexOf(term) > -1)
            this.filteredAlbums.push(album);
      }, this);
  }
}
Esto debería funcionarte, lo que hemos hecho es usar un evento del input que siempre que se produzca (keyup) llamaremos a un método. Esto no deja de ser lo que hemos hecho toda la vida con html y javascript. Ahora veremos un modo más elegante de implementar lo mismo creando otro componete para el filtrado. Llegado este punto es bueno entender que Angular 2 promueve componentes anidados que definen una jerarquía como represento en la imagen de la aplicación que tenemos hasta ahora, a ver si la imagen ayuda.



Vamos a crear entonces nuestro componente de filtrado. Es importante entender el uso de @Output, que lo usamos para indicar que el componente emite un evento de tipo "changed" (podría emitir más de uno).
import { Component, Output, EventEmitter } from 'angular2/core';

@Component({
  selector: 'filter-textbox',
  template: '<input type="text" [(ngModel)]="model.filter" (keyup)="filterChanged($event)"/>'
})
export class FilterTextboxComponent {

    model: { filter: string } = { filter: null };
    
    @Output()
    changed: EventEmitter<string> = new EventEmitter();

    filterChanged(event: any) {
        event.preventDefault();
        this.changed.emit(this.model.filter); //Raise changed event
    }
}
El siguiente paso es sustituir el input que hemos añadido anteriormente a la vista de álbumes por este, fijaros en el selector "filter-textbox".
<filter-textbox class="navbar-right" (changed)="filterChanged($event)"></filter-textbox>
Finalmente vamos al componente de lista de álbum y en el bloque @Component añadiremos la sección directives donde le indicaremos que queremos anidar FilterTextBoxComponent. No olvidéis también indicar en la sección import donde está ubicado el componente FilterTextBoxComponent.
...
import { FilterTextBoxComponent } from './filterTextbox.component';

@Component({ 
  selector: 'customers', 
  providers: [DataService],
  templateUrl: 'app/customers/customers.component.html',
  directives: [FilterTextboxComponent],
})

export ...
Hasta aquí el post de hoy, hemos visto una introducción muy básica a Angular 2. Por mi parte seguiré desarrollando tanto la aplicación en Angular 2 como la Api en asp.net Core, la idea es una vez lo tenga más definido es subirlo a un repo de Github. Como siempre a medida que vaya haciendo si veo algo interesante para un post lo iré contando en areaTIC. Si te ha gustado el post, aprovecho para comentarte que puedes seguir areaTIC en las redes sociales o bien puedes seguirme en twitter @canizarescarlos no te cortes a comentarme lo que sea o dudas... todo amigo de la tecnología es mi amigo :) Que vaya bien!

No hay comentarios:

Publicar un comentario