martes, 3 de enero de 2017

Angular 2. Routing. Entrega 11. Componente de navegacion (2)

En el tutorial de Angular2, se plantea tener un elemento que gestione toda la navegación. Esta navegación puede contener parámetros etc. Para ello se definen las navegaciones posibles en una constante.

Se puede ver el ejemplo oficial en plunker que esta muy bien.


1. Fichero index.html

Hay que colocar en primer lugar despues del <head> de index.html esto:


<base href="/">

2. El fichero del módulo

Este fichero app.module.ts, debe de recoger toda la información de los elementos y componentes utilizados, por tanto es muy importante no dejarse ninguno. En este módulo se deberá:

1. Hacer import de lo básico NgModuleBrowserModuleFormsModule
2. Hacer import de los componentes y servicios que hemos creado, pero también del componente gestor de navegación.
3. Dentro de @NgModule() en los arrays de :
  3.1 imports se incluirá BrowserModule, FormsModule, y AppRoutingModule.
  3.2 declarations se incluirán solo los componentes AppComponent, DashboardComponent,           HeroDetailComponent,  HeroesComponent
  3.3 providers se incluirá el servicio HeroService.
  3.4 bootstrap se incluira AppComponent que es el que arranca la aplicación

Veamos el código de app.module.ts:

import { NgModule }       from '@angular/core';
import { BrowserModule }  from '@angular/platform-browser';
import { FormsModule }    from '@angular/forms';

import { AppComponent }         from './app.component';
import { DashboardComponent }   from './dashboard.component';
import { HeroDetailComponent }  from './hero-detail.component';
import { HeroesComponent }      from './heroes.component';
import { HeroService }          from './hero.service';

import { AppRoutingModule }     from './app-routing.module';

@NgModule({
  imports: [
    BrowserModule,
    FormsModule,
    AppRoutingModule
  ],
  declarations: [
    AppComponent,
    DashboardComponent,
    HeroDetailComponent,
    HeroesComponent
  ],
  providers: [ HeroService ],
  bootstrap: [ AppComponent ]
})
export class AppModule { }

3. Componente que gestiona la navegación

Este componente debe:
1. Tener un import RouterModule y Routes para tener acceso a la navegación.
2. Tener un import a cada componente que se accede con las Routes,
3. Crear una constante tipo Routes que tenga el path a acceder y el componente a utilizar
4. En dicha constante pueden haber rutas con parámetros
4. En la costante Route también debe tener un redirectTo para el path vacío.
5. En imports de @NgModule debe estar RouterModule.forRoot(routes).
6. En exports de @NgModule debe estar RouterModule.

Veamos el ejemplo del código app-routing.module.ts:

import { NgModule }             from '@angular/core';
import { RouterModule, Routes } from '@angular/router';

import { DashboardComponent }   from './dashboard.component';
import { HeroesComponent }      from './heroes.component';
import { HeroDetailComponent }  from './hero-detail.component';

const routes: Routes = [
  { path: '', redirectTo: '/dashboard', pathMatch: 'full' },
  { path: 'dashboard',  component: DashboardComponent },
  { path: 'detail/:id', component: HeroDetailComponent },
  { path: 'heroes',     component: HeroesComponent }
];

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


4. Servicio que obtiene el objeto por el id

Este componente debe:
1. Hacer un import de Injectable, ya que es un servicio.
2. Tener la anotación @Injectable().
3. Tener un método de tipo Promesa<objetoADevolver> que obtenga el objeto con id dado.
Veamos el código del componente del servicio hero.service.ts

import { Hero } from './hero';
import { HEROES } from './mock-heroes';
import { Injectable } from '@angular/core';

@Injectable()
export class HeroService {
  getHeroes(): Promise<Hero[]> {
    return Promise.resolve(HEROES);
  }

  getHeroesSlowly(): Promise<Hero[]> {
    return new Promise(resolve => {
      // Simulate server latency with 2 second delay
      setTimeout(() => resolve(this.getHeroes()), 2000);
    });
  }

  getHero(id: number): Promise<Hero> {
    return this.getHeroes()
               .then(heroes => heroes.find(hero => hero.id === id));
  }
}

5. Componente que recoge los heroes y navega al heroe por id mediante el objeto Router en typescript, con pase de parámetros

Este componente recoge totdos los héroes y cuando se selecciona un héroe, navega al detalle de este héroe, y para ello lo enruta con una dirección mas el parámetro id, que utiliza la ruta de la primera clase: { path: 'detail/:id', component: HeroDetailComponent },

1. Debe hacer import de OnInit y Router, junto a lo Hero y HeroService
2. implementar OnInit.
3. Tener un constructor VACIO que tenga de parámetros un Router i un servicio de obtención del objeto.
4. Tener un método ngOnInt que llame a otro que recoja los héroes.
5. El método que recoge todos los héroes llama al servicio y utiliza su método then, que es el que recoge resultado de una promesa.
6. Un método que asigna la variable selectedHero cuando se clicka en ella.
7. Un método que mande navegar a la ruta con el id seleccionado. Para ello utiliza el método navigate. O sea hará una llamada a
  this.router.navigate(['/detail', this.selectedHero.id]);
y con esto, no le hace falta ni inidcar que el componente a utilizar es el HeroDetail, ni debemos hacer un import a HeroDetail, ya que el componente que gestiona la navegación ya lo sabe.
8. NO se tiene que hacer un import del componente que gestiona la navegación ya que se contempla en el app.module.

Veamos el código de heroes.component.ts

import { Component, OnInit } from '@angular/core';
import { Router } from '@angular/router';

import { Hero } from './hero';
import { HeroService } from './hero.service';

@Component({
  moduleId: module.id,
  selector: 'my-heroes',
  templateUrl: 'heroes.component.html',
  styleUrls: [ 'heroes.component.css' ]
})
export class HeroesComponent implements OnInit {
  heroes: Hero[];
  selectedHero: Hero;

  constructor(
    private router: Router,
    private heroService: HeroService) { }

  getHeroes(): void {
    this.heroService.getHeroes().then(heroes => this.heroes = heroes);
  }

  ngOnInit(): void {
    this.getHeroes();
  }

  onSelect(hero: Hero): void {
    this.selectedHero = hero;
  }

  gotoDetail(): void {
    this.router.navigate(['/detail', this.selectedHero.id]);
  }
}

6. Componente que navega a otros componentes con <nav> en el template. Pase de parámetros

Este componente es muy sencillo ya que no le hace falta referenciar al componente que gestiona la navegación ya que se declara en el app.module.ts. Y tendrá en el template:

1. Un elemento <nav>
2. Tantos elementos <a routerLink="/rutaPropuesta" .. como enlaces propongamos
3. Si queremos enrutar con un parámetro como el id se puede hacer de 4 maneras, todas ellas equivalentes:
   3.1 <a routerLink="/detail/15" routerLinkActive="active">Heroe 15</a>
     Aquí metemos la ruta a piñón fijo.
   3.2 <a routerLink="/detail/{{i}}" routerLinkActive="active">Heroe {{i}}</a>
         Aquí le pasamos una variable i que tiene el id del héroe
   3.3 <a [routerLink]="['/detail',j]" routerLinkActive="active">Heroe {{j}}</a>
         Aquí le pasamos una variable j que tiene el id del héroe, pero routerlink es un objeto y va entre corchetes
   3.4 <a [routerLink]="['/detail',j, {foo:'kk']" routerLinkActive="active">Heroe {{j}}</a>
         Aquí le pasamos una variable j que tiene el id del héroe, pero también le pasamos un parámetro opcional "foo" que vale "kk", siendo routerlink es un objeto que va entre corchetes
 <a [routerLink]="['/detail', k ,{ foo: 'tatia' }]" routerLinkActive="active">Heroe {{k}}</a>
4. Cerrar el nav  con </nav>
5. Al final incluir <router-outlet></router-outlet>
Veamos su código

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

@Component({
  moduleId: module.id,
  selector: 'my-app',
  template: `
    <h1>{{title}}</h1>
    <nav>
      <a routerLink="/dashboard" routerLinkActive="active">Dashboard</a>
      <a routerLink="/heroes" routerLinkActive="active">Heroes</a>
      <a routerLink="/detail/15" routerLinkActive="active">Heroe 15</a>
      <a routerLink="/detail/{{i}}" routerLinkActive="active">Heroe {{i}}</a>
      <a [routerLink]="['/detail',j]" routerLinkActive="active">Heroe {{j}}</a>
      <a [routerLink]="['/detail', k ,{ foo: 'tatia' }]" routerLinkActive="active">Heroe {{k}}</a>

    </nav>
    <router-outlet></router-outlet>
  `,
  styleUrls: ['app.component.css'],
})
export class AppComponent {
  i=12;
  j=13;
  k=14;
  title = 'Tour of Heroes';
}

7. Componente que se llama con un parámetro en la dirección

En este caso tenemos que recoger el parámetro para saber que héroe recoger. Para ello, este componente debe:

1. Hacer import de ActivatedRoute, ParamsLocation para poder trabajar con rutas, además de Component, OnInit y los componentes que utiliza como el servicio HeroService y el componente Hero que nos da la estructura de hero.
2. También debe incorporar el switchMap de reactive
2. Implementar OnInit.
3. Tener un constructor VACIO que tenga de parámetros un ActivatedRoute, Location (para saber donde estamos en el navegador y poder regresar a la página que lo llamó).  y un servicio de obtención del objeto HeroService.
4. Tener un método ngOnInt que recoja los parámetros de llamada (en este caso el id) y que recoja nuestro registro desde el servicio. Para elo utiliza observables
5. Tener una función para volver a la página anterior:  this.location.back();

Veamos el código de hero-detail.component.ts:

import 'rxjs/add/operator/switchMap';
import { Component, OnInit }      from '@angular/core';
import { ActivatedRoute, Params } from '@angular/router';
import { Location }               from '@angular/common';

import { Hero }         from './hero';
import { HeroService }  from './hero.service';
@Component({
  moduleId: module.id,
  selector: 'my-hero-detail',
  templateUrl: 'hero-detail.component.html',
  styleUrls: [ 'hero-detail.component.css' ]
})
export class HeroDetailComponent implements OnInit {
  hero: Hero;

  constructor(
    private heroService: HeroService,
    private route: ActivatedRoute,
    private location: Location
  ) {}

  ngOnInit(): void {
    this.route.params
      .switchMap((params: Params) => this.heroService.getHero(+params['id']))
      .subscribe(hero => this.hero = hero);
  }

  goBack(): void {
    this.location.back();
  }








Angular 2. Routing. Entrega 10. Navegacion simple(1)

1. Añadir navegación al app.module

Para ello, en app.module.ts:
1. Añadimos  import { RouterModule } from '@angular/router';
2. Creamos una constante que contenga todas las rutas:


const appRoutes: Routes = [
  { path: 'crisis-center', component: CrisisListComponent },
  { path: 'heroes',       component: HeroesComponent } ];
       
3. Indicamos el método RouterModule.forRoot en el imports de @NgModule. Que indica la ruta (path) que asignamos al componente. Se puede asignar la ruta directamente o mejor asignarle la constante anterior.

imports: [

              RouterModule.forRoot([
      {path: 'heroes',component: HeroesComponent}
    ])
],
....
o mucho mejor
@NgModule({
  imports: [
    RouterModule.forRoot(appRoutes)
    // other imports here
  ],
  ...
})
export class AppModule { }

4. Añadir  <a routerLink="/heroes">Heroes</a>  en el template dentro de un <nav>.
5. Añadir  <router-outlet></router-outlet> al final del template.
template: `
  <h1>Angular Router</h1>
  <nav>
    <a routerLink="/crisis-center" routerLinkActive="active">Crisis Center</a>
    <a routerLink="/heroes" routerLinkActive="active">Heroes</a>
  </nav>
  <router-outlet></router-outlet>
`

2. Añadir mas componentes

Para ello se añadirán mas elementos al array del método RouterModule.forRoot en el imports de @NgModule. ( o mejor en la constante appRoutes)


               RouterModule.forRoot([
      {path: 'heroes',component: HeroesComponent},
      {path: 'otro',component: OtroComponent}

    ])
También se añadirá otro <a routerLink="/otro">Heroes</a>  en el template dentro del <nav>. Quedando el template:
template: `
   <h1>{{title}}</h1>
   <nav>
     <a routerLink="/heroes">Heroes</a>
     <a routerLink="/otro">Otro</a>
   </nav>
   <router-outlet></router-outlet>
 `

3. Redirect to

Para ello se añadirán otro elementos al array del método RouterModule.forRoot en el imports de @NgModule, que haga referencia al redirect ( o mejor en la constante appRoutes)

               RouterModule.forRoot([
      {path: 'heroes',component: HeroesComponent},
      {path: 'otro',component: OtroComponent},
      {path: '',redirectTo:'/dashboard', pathMatch: 'full'}
    ])

4. Navegación con parámetro (NO)

Para ello se añadirán otro elementos al array del método RouterModule.forRoot en  el imports de @NgModule, que haga referencia a una ruta al que se le añade "/:parametro"

  { path: 'detail/:id' , component: HeroDetailComponent }

En este caso le pasamos el id como parámetro a la ruta.

Pero en el componente se debe hacer estos import
// Keep the Input import for now, we'll remove it later:
import { Component, Input, OnInit } from '@angular/core';
import { ActivatedRoute, Params }   from '@angular/router';
import { Location }                 from '@angular/common';
Y además se debe importar el servicio que nos proporciona el Hero a partir del id.

Pero entonces la cosa se complica y para ello se va a utilizar una nueva estrategia en el punto siguiente.

Angular 2. Formularios. Entrega 9: Templates (8). Template expression operators. Operador safe navigation

Se utiliza este operador ( ? )para evitar hacer referencias a variables que son nulas o que tienen alguna propiedad nula, sin producir la ruptura del programa

Veamos un ejemplo
The current hero's name is {{currentHero?.firstName}}




lunes, 2 de enero de 2017

Angular 2. Formularios. Entrega 8: Templates (7). Template expression operators. Operador pipe

Las template expressions utilizan un subconjunto de la sintaxis de JavaScript con unos pocos operadores para escenarios específicos. Solamente explicaramos 2 operadores el pipe y el safe navegation. En esta entrada solo hablaremos del pipe.

1. El operador pipe (|). Introducción

Al igual que se hace en unix, el operador pipe realiza transformaciones ya sea para filtrar, ordenar, convertir a mayúscula etc.

Veamos las utilidades que tiene de serie:

1. Convertir a mayúscula {{title | uppercase}}
2. Convertir a minúscula {{title | lowercase}}
3. Encadenamiento de conversiones {{title | uppercase | lowercase}}
4. Dar formato como fecha {{currentHero?.birthdate | date}}
5. Aplicar parámetros {{currentHero?.birthdate | date:'longDate'}}
6. Transformar a json (depuración) {{currentHero | json}}
7. Dar formato de moneda {{importe | currency}}
8. Dar formato de porcentaje {{porcentajePerdidas | percent}}
9. Dar formato a un número {{potencia | number}}
10. Devolver el último valor de un obsevable o promesa  {{promesa$ | async}}
11. Internacionalización {{numero | i18nPlural:mapping}} ver mas abajo.
12. Recorte de string o array   {{miString | slice:comienzo[:fin]}}

NOTA: Los pipes de fecha (date) y moneda (currency) necesitan la ECMAScript Internationalization API. Safary y otros navegadores antiguos no la sportan. Se puede añadir un polyfill  en el template.

<script src="https://cdn.polyfill.io/v2/polyfill.min.js?features=Intl.~locale.en"></script>

2. Parámetros de algunos pipes predefinidos

Algunos pipes predefinitos admiten porcentajes:
1. number[:digitInfo]   teniendo digitInfo este formato;
     {minIntegerDigits}.{minFractionDigits}-{maxFractionDigits}
2. percent[:digitInfo] teniendo digitInfo el mismo formato anterior
3. currency[:currencyCode[:symbolDisplay[:digitInfo]]] donde
  currencyCode cumple el ISO 4217 (USD para el dolar y EUR para el euro)
  digitInfo es igual que en apartados anteriorresal anterior y
  symbolDisplay es booleano e indica si se muestra el símbolo de la moneda o no

3. date[:format] donde el formato puede ser:
  3.1 'medium' (yMMMdjms) , 'short' (yMdjm): Para indicar fecha y hora
  3.2 'fullDate' (yMMMMEEEEd), 'longDate' (yMMMMd), 'mediumDate'(yMMMd),
         'shortDate'(yMd): para inidicar fechas
  3.3 'mediumTime'(jms) y 'shortTime'(jm): Para indicar horas

  Además se contemplan los siguientes símbolos:
  3.4 : G (era), y (year), M (month), d (day), E (weekday),
  3.5:  j (hour), h (hour12), H (hour24), m (minute), s (second)
  3.6: z (timezone), Z (timezone), a (timezone)

Veamos un ejemplo con fechas:
<p>Today is {{today | date}}</p
<p>Or if you prefer, {{today | date:'fullDate'}}</p>
<p>The time is {{today | date:'jmZ'}}</p>

4. i18nPlural:mapping debiendo cumplir mapping el formato ICU.
5. islice:comienzo[:fin]

3. Parámetros con expresiones

Pero estos parámetros a parte de poder admintir constantes (numericas de string etc), tambien adminten template expressions.

Veamos un ejemplo donde cambiamos la variable miFormato de shortDate a fullDate.
En el template:
 <p>The hero's birthday is {{ birthday | date:miFormato }}</p>
  <button (click)="toggleFormat()">Toggle Format</button>
En el componente:
export class HeroBirthday2Component {
  birthday = new Date(1988, 3, 15); // April 15, 1988
  toggle = true; // start with true == shortDate

  get miFormato()   { return this.toggle ? 'shortDate' : 'fullDate'; }
  toggleFormat() { this.toggle = !this.toggle; }
}


4. Creación de pipes a medida

Para ello tenemos que:
1. Utilizar la anotación @Pipe ({name: 'miPipe'}) a la que pasamos el nombre miPipe 
2. Realizar la importación en el import de  Pipe y PipeTransform
3. Debemos incluir nuestro pipe en declarations del AppModule.
4. La clase pipe debe implementar el método transform el interface PipeTransform que admite valores de entrada y otros valores opcionales y devuelve el valor transformado.

Veamos un ejemplo que damos de nombre al pipe exponentialStrength, al que le pasamos 2 números, y el primer número lo elevamos al segundo, El segundo número es opcional.
Vemos el pipe (app/exponentual.strength.pipe.ts)
import { Pipe, PipeTransform } from '@angular/core';
/*
 * Raise the value exponentially
 * Takes an exponent argument that defaults to 1.
 * Usage:
 *   value | exponentialStrength:exponent
 * Example:
 *   {{ 2 |  exponentialStrength:10}}
 *   formats to: 1024
*/
@Pipe({name: 'exponentialStrength'})
export class ExponentialStrengthPipe implements PipeTransform {
  transform(value: number, exponent: string): number {
    let exp = parseFloat(exponent);
    return Math.pow(value, isNaN(exp) ? 1 : exp);
  }
}
Veamos el template (2 es el primer parámetro y 10 el segundo)
<h2>Power Booster</h2>
<p>Super power boost: {{2 | exponentialStrength: 10}}</p>
También se pueden utilizar template expressions en el template tal como vimos anteriormente.

Hay que tener cuidado con las actualizaciones del contenido de un array. Angular tiene una referencia a un array al que podemos aplicarle un pipe. Si añadimos elementos al array, la referencia al array no cambia, por tanto, no actualiza la pantalla. Si queremos que cambie, se tiene que cambiar la referencia a un nuevo array. (En C, Java,.. y la mayoria de lenguajes, se guarda la referencia o posicion de memoria del array u objeto, sin importarnos en primera instancia el contenido del mismo)


5. Pipes puros e impuros

Un pipe impuro permite detectar el cambio del contenido de un array u objeto. Pero tiene la desventaja que consume muchos mas recursos del sistema para ir testeando el componente.

Para inidicar que un pipe es impuro, solamente hay que indicar pure: false en fla anotación
@Pipe({
  name: 'flyingHeroesImpure',
  pure: false
})

6. El pipe AsincPipe

Es un pipe que es impuro que se aplica a una promesa o observable, devolviendo los valores que se emiten. Es stateful, ya que nos reporta los valores del observable a medida que nos llegan.

Veamos un ejemplo de un compopnente

  1. import { Component } from '@angular/core';
  2. import { Observable } from 'rxjs/Observable';
  3. import './rxjs-extensions';
  4. @Component({
  5. selector: 'hero-message',
  6. template: `
  7. <h2>Async Hero Message and AsyncPipe</h2>
  8. <p>Message: {{ message$ | async }}</p>
  9. <button (click)="resend()">Resend</button>`,
  10. })
  11. export class HeroAsyncMessageComponent {
  12. message$: Observable<string>;
  13. private messages = [
  14. 'You are my hero!',
  15. 'You are the best hero!',
  16. 'Will you be my hero?'
  17. ];
  18. constructor() { this.resend(); }
  19. resend() {
  20. this.message$ = Observable.interval(500) // cada 500 mseg emite un entero
  21. // comenzando por 0 e incrementando en 1
  22. // o sea 0,1,2,3,4,5,6....
  23. .map(i => this.messages[i]) //asignamos el mensaje i-esimo
  24. .take(this.messages.length); // paramos pasada la longitud del vector
  25. }
  26. }

7. Filter & Sort

Angular ya no suministra pipes para filtrar y ordenar ya que bajan el rendimiento. Para ello cada cual debe proporcionar estas herramientas desde el componente.