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();
  }








2 comentarios :

  1. Este comentario ha sido eliminado por el autor.

    ResponderEliminar
  2. Por favor, qué hace:
    this.route.params
    .switchMap((params: Params) => this.heroService.getHero(+params['id']))
    .subscribe(hero => this.hero = hero);

    para qué se usa el switchMap?
    Puedes explayarte un poco en tu explicación como si fuera un niño de 5 años??? ;-)

    ResponderEliminar