Angular 2

Jesús Rodríguez

Consultor en IdeaBlade

  • Intro
  • @Decoradores
  • Componentes
  • Entrada y salidas
  • Directivas
  • Servicios
  • Tuberías
  • NgModule y bootstrap
  • Http
  • Router
  • angular-cli

Agenda

Prerrequisitos

  • Framework del lado del cliente.
  • Impulsado por Google.
  • Angular 1 tuvo una gran cuota de mercado. Angular 2 ha sido reescrito desde cero.
  • Promociona TypeScript como lenguaje recomendado.

Angular 2

Soporte en navegadores

Funciones que añaden metadatos a una clase, función o propiedad.

@Decoradores

class UserService {
  getUser(id: number) {
    return { name: 'Fake', email: "fake@email.com" }
  }
  
  getUsers() {
    return { // muchos usuarios }
  }
}

Queremos hacer log de todas las funciones llamadas:

@Decoradores

class UserService {
  getUser(id: number) {
    // Algunas lineas para hacer logging
    return { name: 'Fake', email: "fake@email.com" }
  }
  
  getUsers() {
    // Algunas lineas para hacer logging
    return { // muchos usuarios }
  }
}

Podemos crear un decorator y marcar las funciones a loguear.

@Decoradores

export function log(target: Function, key: string, value: any) {
  return {
    value: function (...args: any[]) {
      // lógica del logging
      return originalResult;
    }
  }
}

Aplicamos nuestro nuevo decorador donde lo necesitemos:

@Decoradores

class UserService {
  @log
  getUser(id: number) {
    return { name: 'Fake', email: "fake@email.com" }
  }

  @log
  getUsers() {
    return { // muchos usuarios }
  }
}

O también a nivel de clase:

@Decoradores

@loggable
class UserService {
  getUser(id: number) {
    return { name: 'Fake', email: "fake@email.com" }
  }

  getUsers() {
    return { // muchos usuarios }
  }
}

Más información: https://goo.gl/WYt26m

Hola mundo

@Component({
  selector: '...'
  template: `...`,
  styles: `...`,
  animations: '...'
  // muchas más propiedades
})

Las aplicaciones Angular están hechas con componentes:

export class MyComponent { }

Vamos a ver cómo es un "Hola mundo":

Hola mundo

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

@Component({
  selector: 'gr-app',
  template: `<div>Hola mundo</div>`
})
export class AppComponent { }

Esto más que mundo es Golden Race, ¿no?

Hola... Golden Race?

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

@Component({
  selector: 'gr-app',
  template: `<div>Hola {{name}}</div>`,
})
export class AppComponent {
  name = 'Golden Race';
}

Entrada (Input):

Entrada y salida

<gr-component home="Manchester"></gr-component>
<gr-component [away]="awayTeam"></gr-component>
<img src="http://someimage.com"></img>
<img [src]="teamLogo"></img>

Salida (Output):

<div (click)="selectTeam()">I like this team</div>
<div (keyup.enter)="confirm($event)">Confirm selection</div>

Entrada y salida (two-way databinding):

<gr-component [(team)]="team"></gr-component>

Corchetes, no corchetes, paréntesis, los dos a la vez... ¿Qué es lo próximo? ¿Interrogaciones? ¿Exclamaciones?

Entrada y salida

Un ejemplo real de Angular 1:

<gr-component select="team.city(current)"></gr-component>
<gr-component date="{{data}}"></gr-component>
<gr-component date="date"></gr-component>
<gr-component date="'date'"></gr-component>

Entrada y salida

¿Cómo soluciona Angular 2 este problema?

Introducir texto como entrada:

<gr-component home="Milan"></gr-component>

Introducir una variable como entrada:

<gr-component [home]="home"></gr-component>

Recibir un evento del componente:

<gr-component (select)="apply()"></gr-component>

Entrada y salida

¿Y de lado del componente?

import {
  Component,
  Input,
  EventEmitter,
  Output
} from '@angular/core';

Component({...})
export class BetComponent {
  @Input() home: string;
  @Input() away: string;
  
  @Output() select = new EventEmitter<string>();
}

Entrada y salida

¿Y en el template?

template: `
  <h2>Place your bet:</h2>
  <p (click)="onSelect(home)">home: {{home}}</p>
  <p (click)="onSelect(away)">away: {{away}}</p>
`
onSelect(team) { this.select.emit(team); }

Emitimos el evento:

Entrada y salida

Ahora usamos el componente:

<gr-bet [home]="home" away="Liverpool" (select)="onSelect($event)"></gr-bet>
export class AppComponent {
  home = 'Málaga';
  selected: string;
  
  onSelect(event) {
    this.selected = event;
  }
}

Two-way databinding: https://goo.gl/IUghvM

Directivas

Los componentes son un tipo de directiva, ¿para qué se usan las directivas?

import { Directive, ElementRef, Input } from '@angular/core';

@Directive({ selector: '[grHighlight]' })
export class HighlightDirective {
    constructor(el: ElementRef) {
       el.nativeElement.style.backgroundColor = ‘yellow’;
    }
}
<p myHighlight>Your team won!</p>

Directivas incluidas

*ngIf - Ocultar o mostrar un elemento:

@Component({
  selector: 'gr-app',
  template: `
    <p>You won't see anything below this</p>
    <div *ngIf="show">
      <h2>You can't see me :(</h2>
    </div>
  `,
})
export class AppComponent {
  show = false;
}

Directivas incluidas

*ngFor - repetir un elemento múltiples veces:

@Component({
  selector: 'gr-app',
  template: `
    <ul>
      <li *ngFor="let team of teams">
        {{team.name}} from {{team.country}}
      </li>
    </ul>
  `,
})
export class AppComponent {
  teams = [
    { name: 'Madrid', country: 'Spain' },
    { name: 'Liverpool', country: 'England' },
    { name: 'Milan', country: 'Italy' }
  ];
}

Directivas incluidas

¿Qué hay detrás del asterisco?

template: `
  <ul>
    <template ngFor let-team [ngForOf]="teams">
      <li>
        {{team.name}} from {{team.country}}
      </li>
    </template>
  </ul>
`
template: `
  <template [ngIf]="show">
    <h2>You can't see me :(</h2>
  </template>
`

Servicios

Los servicios nos ayudan a encapsular la lógica de negocio.

export class TeamService {
  constructor(private http: Http) { }
  
  getTeams() {
    return this.http.get('api/teams')
      .map((res) => res.json().data);
  }
  
  getTeam(id: number) {
    return this.http.get(`api/teams/${id}`)
      .map((res) => res.json().data);
  }
}

Servicios

Para consumir un servicio, lo inyectamos donde lo necesitemos:

export class AppComponent implements OnInit {
  teams: any;
  bestTeam: any;
  
  constructor(private teamService: TeamService) { }
  
  ngOnInit() {
    this.teamService.getTeams()
        .subscribe(res => this.teams = res; );

    this.teamService.getTeam(12)
        .subscribe(res => this.bestTeam = res; );
  }
}

Servicios

Hablando de servicios... ¿qué es y cómo funciona la inyección de dependencias?

export class AppComponent {
  constructor(public foo: Foo, bar: Bar, public baz: Baz) {}
}

Es como una caja donde guardamos las instancias de nuestros servicios.

Servicios

Pero... si al transpilar se pierde el tipado, ¿cómo sabe Angular qué tiene que inyectar?

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

@Injectable()
export class TeamService (
  constructor(private http: Http) { }
  ...
}
__metadata('design:paramtypes',
           [(typeof (_a = typeof http_1.Http !== 'undefined'
                       && http_1.Http) === 'function'
                       && _a) || Object]
           )

Tuberías

Los pipes (o tuberías) nos permiten transformar una entrada de la forma que necesitemos:

import { Pipe, PipeTransform } from '@angular/core';

@Pipe({ name: 'capitalize'})
export class CapitalizePipe implements PipeTransform {
  transform(value: string) {
    return value[0].toUpperCase() + value.substring(1); 
  }
}
@Component({
  selector: 'gr-app',
  template: `{{ name | capitalize }}`
})
export class AppComponent {
  name = 'golden race';
}

Tuberías

También son capaces de aceptar parámetros:

@Pipe({ name: 'capitalize'})
export class CapitalizePipe implements PipeTransform {
  transform(value: string, all: boolean) {
    if (all) {
      return value.split(' ').map(word => {
        return word[0].toUpperCase() + word.substring(1);
      }).join(' ');
    } else {
      return value[0].toUpperCase() + value.substring(1); 
    }
  }
}
template: `{{ name | capitalize: true }}`

NgModule y bootstrap

Las distintas piezas de Angular 2 se organizan dentro de módulos:

@NgModule({
  imports: [ BrowserModule, HttpModule ],
  declarations: [ AppComponent, BetComponent, CapitalizePipe ],
  providers: [ TeamService ],
  bootstrap: [ AppComponent ]
})
export AppModule {}

Puedes tener todo en un solo módulo, o dividirlo entre las distintas secciones de tu aplicación. Incluso puedes cargar un módulo sólo cuando sea necesario (lazy loading).

NgModule y bootstrap

Los módulos de secciones (features) declararán y exportarán sus componentes y opcionalmente algún módulo:

@NgModule({
  imports: [ CommonModule ],
  declarations: [ TeamComponent, TeamDetailComponent ],
  exports: [ TeamComponent ]
})
export class TeamModule { }

NgModule y bootstrap

Uno de los módulos será el que usemos para lanzar la aplicación:

import {platformBrowserDynamic} from '@angular/platform-browser-dynamic';
import {AppModule} from './app.module';

platformBrowserDynamic().bootstrapModule(AppModule)

Y usaremos el selector del componente de bootstrap en nuestro index.html:

<gr-app>loading...</gr-app>

Plunker: https://goo.gl/Y7fpWy (el mismo de tuberías)

Http

Con Http nos podemos comunicar con el servidor.

import { HttpModule } from '@angular/http';

@NgModule({
  imports: [ ... , HttpModule, ... ],
  ...
})

Importamos:

Inyectamos y usamos:

export class UserService {
  constructor(private http: Http) { }

  getUsers() {
   return this.http.get('api/users');
  }
}

Http

Podemos transformar el resultado usando rxjs:

export class TeamService {
  constructor(private http: Http) { }
  
  getTeams() {
    return this.http.get('api/teams')
      .map((res) => res.json().data);
  }
}

Y si no queremos trabajar con observables, siempre podemos transformar la respuesta a una promesa:

export class TeamService {
  constructor(private http: Http) { }
  
  getTeam(id: number) {
    return this.http.get(`api/teams/${id}`)
      .map((res) => res.json().data)
      .toPromise();
  }
}

Http - Promesas vs Observables

  • Las promesas son un patrón que trata de eliminar el callback-hell proporcionando un API más limpio.

 

  • Los observables van un paso más allá y transforman streams de datos reactivos por donde fluyen los cambios (rxjs). Es un modelo de trabajo más declarativo.

 

  • HttpModule y otros módulos de Ng2 usan Observables como API base y lo exponen como tal. Es el modo recomendado.

Router

El router de Angular 2 es un router de componentes. Eso quiere decir que mapea cada ruta a un componente a mostrar:

const routes: Routes = [
  { path: '', redirectTo: '/teams', pathMatch: 'full' },
  { path: 'teams',     component: TeamsComponent },
  { path: 'teams/:id', component: TeamDetailComponent },
  { path: 'bet',       component: BetComponent }
];

Y finalmente exporta un módulo con las rutas:

@NgModule({
  imports: [ RouterModule.forRoot(routes) ],
  exports: [ RouterModule ]
})
export class AppRoutingModule {}

Router

Podemos navegar a otras rutas de dos maneras:

constructor(private router: Router) { }

gotoTeams() {
  this.router.navigate(['/teams']);
}
<a routerLink="/teams">Teams</a>

Desde nuestro template:

O desde el componente:

Router

Si desde "teams" queremos navegar al detalle de uno de los equipos, podemos crear una ruta relativa:

<a [routerLink]="[team.id]">{{ team.name }}</a>
constructor(private route: ActivatedRoute) { }

ngOnInit() {
  this.id = this.route.snapshot.params['id'];
}

Desde TeamDetailComponent podemos acceder al id así:

Router

Por muchas rutas que tengamos, seguimos necesitando un sitio donde alojar esas páginas:

<router-outlet></router-outlet>

router-outlet será reemplazado con el template del componente enrutado.

angular-cli

angular-cli es una de las opciones que tenemos para crear nuestras aplicaciones:

$ npm install -g angular-cli

La gran ventaja de angular-cli es que todas las herramientas no son visibles al usuario, lo que facilita a un equipo el trabajo y permite al cli actualizarse sin conflictos.

$ ng new goldenrace

$ ng serve

$ ng test

Angular 2

By Jesus Rodriguez

Angular 2

  • 464