Multiples estados

Actualmente contamos con una sola pantalla que muestra todas las noticias actuales y un pequeño formulario para poder agregar una nueva noticia. Deseamos que cuando el usuario haga click en alguna noticia la aplicación pase a un nuevo estado en dónde se muestre esa sola noticia en pantalla junto a la lista de sus comentarios.

Vamos a crear un nuevo componente para este nuevo "estado". Lo llamaremos PostDetailComponent

src/frontend/app/components/postDetail.component.js

import { Component } from '@angular/core'; @Component({ selector: 'postDetail', inputs: [ 'post' ], template: `<post [data]="post"></post> <h2>Comentarios:</h2> <comment *ngFor="let comment of post.comments" [data]="comment"></comment>` }) export default class PostDetailComponent { }

Observamos dos cosas:

  • Reusa al componente PostComponent que ya habíamos definido con anterioridad. Gol.
  • Hace referencia a un componente <comment> que es cotrolado por CommentComponent. El mismo luce algo así:

src/frontend/app/components/comment.component.js

import { Component } from '@angular/core'; @Component({ selector: 'comment', inputs: [ 'data' ], template: `<div class="comment"> {{data.body}} <span class="author">{{data.author}}</span> </div>` }) export default class CommentComponent { }

Estados de nuestra aplicación

Entonces desde ahora podemos decir que nuestra app tiene dos estados posibles:

  • Uno representado por PostDetailComponent que renderiza una noticia con sus comentarios
  • Otro que renderice a todas las noticias y al form para crear una nueva. Actualmente se encuentra dentro de AppComponent pero es una buena idea introducir un refactor y llamar a todo eso PostListComponent.

src/frontend/app/components/postList.component.js

import { Component } from '@angular/core'; import PostService from '../services/post.service'; @Component({ selector: 'postList', template: `<post *ngFor="let item of posts" [data]="item"></post> <newPost></newPost>` }) export default class PostListComponent { constructor(postService) { this.posts = postService.posts; } } PostListComponent.parameters = [ [PostService] ]

Conservaremos AppComponent como el punto de entrada a nuestra aplicación que delegará (de momento) solo en el estado PostListComponent

src/frontend/app/components/app.component.js

@Component({ selector: 'app-view', template: `<h1>Bienvenidos a {{name}}</h1> <postList></postList>`, providers: [ PostService ] }) export default class AppComponent { constructor() { this.name = 'Noticias UNQ' } }

Importante. No olivdemos que debemos registrar todos estos estados nuevos en nuestro @NgModule

src/frontend/bootstrap.js

... import AppComponent from './app/components/app.component' import PostComponent from './app/components/post.component' import NewPostComponent from './app/components/newPost.component' import PostDetailComponent from './app/components/postDetail.component' import PostListComponent from './app/components/postList.component' import CommentComponent from './app/components/comment.component' @NgModule({ imports: [ BrowserModule, FormsModule, HttpModule ], styleUrls: ['./style.css'], declarations: [ PostComponent, NewPostComponent, AppComponent, PostDetailComponent, PostListComponent, CommentComponent ], bootstrap: [ AppComponent ] }) class AppModule { }

Angular router

Introduciremos entonces al modulo de angular llamado router. El mismo es justamente una máquina de estados que decide que componente renderizar en función de la url del browser.

Primero nos instalaremos el módulo @angular/router por medio de npm

$ npm install @angular/router --save

En donde configuramos nuestro NgModule (bootstrap.js) importaremos y configuraremos el router de la siguiente forma:

src/frontend/bootstrap.js

... import { RouterModule } from '@angular/router'; let router = RouterModule.forRoot([ { path: '', redirectTo: '/noticias', pathMatch: 'full' }, { path: 'noticias', component: PostListComponent }, { path: 'noticia/:id', component: PostDetailComponent } ], { useHash: true }) ...

Aquí definimos dos urls (o rutas) posibles. La primera /noticias que rederizará el componente PostComponent y la segunda /noticia/:id que renderizará el componente PostDetailComponent. Adicionalmente definimos que cualquier pedido a / sea redirigido a /noticias/

Para completar la configuración del router deberemos cambiar dos cosas:

  1. Debemos registrar router como dependencia de nuestro modulo. Simplemente lo agregaremos a la lista de imports en el decorator @NgModule: imports: [ router, BrowserModule, FormsModule, HttpModule ]

  2. Debemos cambiar nuestro componente AppComponent una vez para no tener harcodeada una referencia a <postList> sino que delegue en el router para representar cualquiera de los estados que se encuentra actualmente activo. Para hacer eso necesitamos cambiar su template por el siguiente:

<h1>Bienvenidos a {{name}}</h1>
<router-outlet></router-outlet>

<router-outlet> es justamente un componente provisto por el RouterModule que se encargará de dinamicamente renderizar uno u otro estado.

Necesitamos definir links para navegar entre estados. Para usaremos la directiva routerLink. Agregaremos dos links, uno en la lista de noticias para ir al detalle y otro en el detalle para volver a la lista de noticias:

src/frontend/components/postList.component.js

... <post *ngFor="let item of posts" [data]="item" [routerLink]="['/noticia', item._id]"></post> <newPost></newPost> ...

y

src/frontend/components/postDetail.component.js

... <post [data]="post"></post> <h2>Comentarios:</h2> <comment *ngFor="let comment of post.comments" [data]="commment"></comment> <a [routerLink]="['/noticias']">Atras</a> ...

La directiva routerLink recibe un array de n posiciones representando segmentos de la ruta a la que apunta (un segmento es cada pedacito de la ruta entre /). Por ejemplo, [routerLink]=['/blah','bleh,'blih'] apuntará a la ruta /blah/bleh/blih.

La razón por la que especificamos el nombre de la directiva entre [] es porque deseamos que bindee (o que reaccione ante cualquier cambio) de la expresión ['/noticia', item._id].

Probamos y casí casí anda! Al hacer click sobre una noticia navegamos al detalle... aunque el detalle se rompe

Pasando parametros entre rutas

El detalle se rompe porque justamente nos falta asignar un valor a this.post. La url con la que llegamos a dicho estado contiene el id de la noticia /noticia/:id, deberíamos poder extraer el valor de dicho id y recuperar la noticia en base a eso.

Para eso necesitaremos que se nos inyecten dos objetos en nuestro componente:

  • ActivatedRoute - la misma contendrá información (y lo que mas nos interesa, los parametros) de la ruta que se encuentra actualmente activa en el router.
  • PostService - para poder recuperar una noticia en base a su id.

Modificaremos PostDetailComponent para recibir esos dos objetos

src/frontend/components/postDetail.component.js

import { Component } from '@angular/core'; import { ActivatedRoute } from '@angular/router'; import PostService from "../services/post.service" @Component(...) export default class PostDetailComponent { constructor(route, postService) { this.route = route this.postService = postService } } PostDetailComponent.parameters = [ ActivatedRoute, PostService ]

Luego utilizamos dichos objetos para recuperar el valor de this.post

src/frontend/components/postDetail.component.js

ngOnInit() { this.post = {} this.route.params.subscribe(params => { //cuando algo un parametro cambia this.postService.getPost(params.id) .then(post => this.post = post) .catch(e => console.log(e)); }); }

Varias cosas:

  • ngOnInit es una función la cuál, si existe, es llamada por angular durante la inicialización de nuestro componente. Sería analoga al constructor pero, por razones de convención, angular aconseja colocar toda inicialización en dicho punto.

  • this.route.params no es un objeto con todos los parametros como uno intuíría sino un objeto de tipo Observable. Los observables entienden el mensaje subscribe con un callback el cual será invocado cada vez que alguno de esos parametros cambie.

  • this.postService.getPost es un nuevo método que agregamos a post service el cuál retorna una promise que se resuelve a una noticia y está implementado de la siguiente forma:

src/frontend/services/post.service.js

... getPost(id) { return this.http.get(`/noticias/${id}`).toPromise() .then(response => response.json()); } ...

GitHub

Como referencia dejamos el estado del proyecto en GitHub

results matching ""

    No results matching ""