Resumen
Angular se ha convertido en uno de los frameworks más populares. Entre sus grandes ventajas destacan el desarollo basado componentes y su polivalencia para desarrollo web y móvil.
En este tutorial se aborda el desarrollo por etapas de una aplicación maestro detalle basada en servicios. El objetivo real es aprender interactuar con servicios REST y navegar entre páginas con llamadas a servicios diferentes y aprender a tratar con la asincronía de las peticiones. No obstante, hay situaciones, como en el desarrollo de un prototipo inicial, en las que puede ser necesario desarrollar servicios con datos mockeados en memoria. Por ello, dedicaremos un espacio a este tipo de servicios que trabajan con datos en memoria, los cuales están exentos de las cuestiones de la asincronía.
-
Crear un proyecto sencillo en Angular
-
Entender el uso de rutas en Angular
-
Usar el componente
Router
para la carga dinámica de páginas -
Usar el patrón de inyección de dependencias para el uso de servicios y captura de parámetros
-
Crear servicios de generación de datos síncronos
-
Crear servicios asíncronos basados en APIs REST
-
Consumir de servicios de forma asíncrona
-
Presentar datos de los componentes en las vistas
Disponible el repositorio usado en este tutorial. |
1. Creación de los elementos básicos del proyecto
El proyecto que vamos a crear está formado iniciamente por dos páginas (home
y about
) e incluirá una barra de navegación en la parte superior. Usaremos también Bootstrap, lo que le dará un aspecto similar al de la figura:
Desde una terminal comenzamos creando el proyecto con el CLI de Angular
ng new miapp (1)
cd miapp
1 | Nos pedirá si queremos añadir enrutado Angular y el tipo de formato de hojas de esilo que queremos usar. Dejamos las opciones por defecto (No usar enrutado Angular y CSS como formato de hojas de estilo) |
1.1. Instalación de Bootstrap
Para el uso de Bootstrap podemos optar por incluir el CSS en la cabecera y los JS al final del archivo index.html
. Esta es una de las formas más populares de usar Bootstrap, aunque no es la forma que seguiremos en este tutorial. A continuación se muestra un fragmento de cómo quedaría el index.html
si siguiéramos esta opción.
...
<head>
...
<link rel="stylesheet" href="https://stackpath.bootstrapcdn.com/bootstrap/4.5.0/css/bootstrap.min.css" integrity="sha384-9aIt2nRpC12Uk9gS9baDl411NQApFmC26EwAOH8WgZl5MYYxFfc+NcPb1dKGj7Sk" crossorigin="anonymous"> (1)
...
</head>
<body>
...
<script src="https://code.jquery.com/jquery-3.5.1.slim.min.js" integrity="sha384-DfXdz2htPH0lsSSs5nCTpuj/zy4C+OGpamoFVy38MVBnE+IbbVYUew+OrCXaRkfj" crossorigin="anonymous"></script> (2)
<script src="https://cdn.jsdelivr.net/npm/popper.js@1.16.0/dist/umd/popper.min.js" integrity="sha384-Q6E9RHvbIyZFJoft+2mJbHaEWldlvI9IOYy5n3zV9zzTtmI3UksdQRVvoxMfooAo" crossorigin="anonymous"></script>
<script src="https://stackpath.bootstrapcdn.com/bootstrap/4.5.0/js/bootstrap.min.js" integrity="sha384-OgVRvuATP1z7JjHLkuOU7Xw704+h835Lr+6QL9UvYjZE3Ipu6Tp75j7Bh/kR0JKI" crossorigin="anonymous"></script>
</body>
...
1 | Inclusión de la hoja de estilos en el <head> |
2 | Inclusión de los JS al final del <body> |
La opción que elegiremos para incluir Bootstrap en nuestro proyecto será usarlo como un paquete más del proyecto siguiendo estos pasos. En primer lugar, instalamos los paquetes necesarios (bootstrap
, jquery
y @popperjs/core
) y usaremos la opción --save
para guardar sus dependencias en el archivo package.json
. Esto hará que estos paquetes se instalen cuando otros utilicen el proyecto e instalen las dependencias con npm install
.
npm install bootstrap jquery @popperjs/core --save
Incluir en la sección architect.build
de angular.json
las referencias al CSS y los JS. Esto es equivalente a incluir en el index.html
el CSS y los JS de Boostrap.
...
"styles": [
"src/styles.css",
"node_modules/bootstrap/dist/css/bootstrap.min.css"
],
"scripts": [
"node_modules/jquery/dist/jquery.min.js",
"node_modules/@popperjs/core/dist/umd/popper.min.js",
"node_modules/bootstrap/dist/js/bootstrap.min.js"
]
...
1.2. Creación de los componentes
Organizaremos los componentes y los servicios de la aplicación en carpetas:
-
Una carpeta
components
para los componentes. -
Una carpeta
componentes/shared
para componentes compartidos (p.e. la barra de navegación) -
Una carpeta
services
para servicios que veremos más adelante cuando creemos el primer servicio.
Creamos los componentes con el CLI de Angular
ng generate component components/home
ng generate component components/about
ng generate component components/shared/navbar
Los componentes generados son añadidos de forma automática al array
|
1 | Componentes incoporados |
1.3. Implementación de la página home
Sustituimos el contenido de components/home.component.html
por un jumbotron de Bootstrap.
<div class="jumbotron">
<h1 class="display-4">Hello, world!</h1>
<p class="lead">
This is a simple hero unit, a simple jumbotron-style component for calling
extra attention to featured content or information.
</p>
<hr class="my-4" />
<p>
It uses utility classes for typography and spacing to space content out
within the larger container.
</p>
<a class="btn btn-primary btn-lg" href="#" role="button">Learn more</a>
</div>
1.4. Implementación de la barra de navegación
Sustituimos el contenido de components/shared/navbar.html
por un navbar sin cuadro de búsqueda de Bootstrap. En las opciones de menú dejamos sólo dos elementos (uno para home
y otro para about
)
<nav class="navbar navbar-expand-lg navbar-light bg-light">
<a class="navbar-brand" href="#">Navbar</a>
<button
class="navbar-toggler"
type="button"
data-toggle="collapse"
data-target="#navbarNav"
aria-controls="navbarNav"
aria-expanded="false"
aria-label="Toggle navigation"
>
<span class="navbar-toggler-icon"></span>
</button>
<div class="collapse navbar-collapse" id="navbarNav">
<ul class="navbar-nav">
<li class="nav-item active">
<a class="nav-link" href="#">Home</a> (1)
</li>
<li class="nav-item">
<a class="nav-link" href="#">About</a> (2)
</li>
</ul>
</div>
</nav>
1 | Elemento Home al que se ha eliminado el <span> que trae por defecto el código del navbar |
2 | Elemento About |
1.5. Enrutado
Las rutas permitidas en nuestra aplicación estarán definidas en el archivo app.routes.ts
. Inicialmente, definiremos una ruta para el home
y otra para el about
. Más adelante, conforme vayamos incorporando funcionalidades a la aplicación añadiremos más rutas a este archivo.
import { Routes } from '@angular/router';
import { HomeComponent } from './components/home/home.component';
import { AboutComponent } from './components/about/about.component';
export const ROUTES: Routes = [
{ path: 'home', component: HomeComponent }, (1)
{ path: 'about', component: AboutComponent },
{ path: '', pathMatch: 'full', redirectTo: 'home' }, (2)
{ path: '**', pathMatch: 'full', redirectTo: 'home' }, (3)
];
1 | Para cada ruta se define un par formado por el path a añadir a la URL de la aplicación junto a su componente asociado. |
2 | Redirigir a home la ruta vacía |
3 | Redirigir a home cualquier otra ruta no válida |
1.6. Modificar la página de la aplicación
La página de la aplicación se construye mediante la inclusión de componentes existentes. En el app.component.html
dejamos sólo los selectores de los componentes que incluiremos en la página (la barra de navegación y el componente seleccionado desde la barra de navegación).
<app-navbar></app-navbar> (1)
<div class="container m-5">
<router-outlet></router-outlet> (2)
</div>
1 | Selector del componente de la barra de navegación |
2 | Marcador que incluye de forma dinámica el componente de la ruta seleccionada |
1.7. Incluir las rutas en la barra de navegación
Para cada elemento de la barra de navegación incluiremos su ruta correspondiente definida previamente en app.routes.ts
<nav class="navbar navbar-expand-lg navbar-light bg-light">
<a class="navbar-brand" href="#">Navbar</a>
<button
class="navbar-toggler"
type="button"
data-toggle="collapse"
data-target="#navbarNav"
aria-controls="navbarNav"
aria-expanded="false"
aria-label="Toggle navigation"
>
<span class="navbar-toggler-icon"></span>
</button>
<div class="collapse navbar-collapse" id="navbarNav">
<ul class="navbar-nav">
<li class="nav-item" routerLinkActive="active"> (1)
<a class="nav-link" routerLink="home">Home</a> (2)
</li>
<li class="nav-item" routerLinkActive="active">
<a class="nav-link" routerLink="about">About</a> (3)
</li>
</ul>
</div>
</nav>
1 | Dejar la clase sólo a nav-item . El estilo active quedará determinado
en función de la ruta seleccionada |
2 | routerLink toma el valor del path definido en app.routes.ts |
3 | Path de la ruta about en app.routes.ts |
2. Creación de un servicio con datos en memoria
Al comenzar a desarrollar una aplicación basada en servicios, como puede ser una aplicación que interactúe con una base de datos a través de una API REST, es frecuente no contar con la API a la hora de desarrollar el frontend. En estas situaciones podemos simular el funcionamiento del servicio generando unos datos de prueba (p.e. en JSON) y desarrollar el frontend a partir de esos datos de prueba. Una vez desarrollada la API REST se podrán cambiar los servicios para que interactúen con la base de datos en lugar de los datos de prueba.
2.1. Creación del servicio
Los componentes de Angular no deben interactuar directamente con los datos. Los componentes deben dedicarse a presentar los datos y delegando en los servicios el acceso a los datos. Posteriormente, los servicios se inyectarán en los componentes que quieran usarlos (para más información consultar la inyeccion de dependencias en Angular).
ng generate service services/employees (1)
1 | Guardaremos los servicios en una carpeta services |
Añadir el servicio creado al array providers
en app.module.ts
import { BrowserModule } from '@angular/platform-browser';
import { NgModule } from '@angular/core';
import { AppComponent } from './app.component';
import { HomeComponent } from './components/home/home.component';
import { AboutComponent } from './components/about/about.component';
import { NavbarComponent } from './components/shared/navbar/navbar.component';
import { RouterModule } from '@angular/router';
import { ROUTES } from './app.routes';
import { EmployeesService } from './services/employees.service'; (1)
@NgModule({
declarations: [AppComponent, HomeComponent, AboutComponent, NavbarComponent],
imports: [BrowserModule, RouterModule.forRoot(ROUTES, { useHash: true })],
providers: [EmployeesService], (2)
bootstrap: [AppComponent],
})
export class AppModule {}
1 | Archivo del servicio |
2 | Servicio añadido al array providers |
A diferencia de los componentes, que sí son añadidos de forma automáticamente al array |
2.2. Implementación del servicio
Para crear el mock y evitar la interacción con una base de datos crearemos un array donde guardaremos los datos en JSON, tal y como los devolvería la API que está pendiente de crear.
import { Injectable } from '@angular/core';
@Injectable({
providedIn: 'root',
})
export class EmployeesService {
employees: any[] = [ (1)
{
id: 0,
isActive: false,
age: 39,
name: {
first: 'Patsy',
last: 'Moore',
},
company: 'ZYTREX',
email: 'patsy.moore@zytrex.net',
favoriteFruit: 'strawberry',
},
{
id: 1,
isActive: true,
age: 37,
name: {
first: 'Valencia',
last: 'Flores',
},
company: 'AQUAMATE',
email: 'valencia.flores@aquamate.us',
favoriteFruit: 'banana',
},
{
id: 2,
isActive: false,
age: 37,
name: {
first: 'Leona',
last: 'Wyatt',
},
company: 'SENMAO',
email: 'leona.wyatt@senmao.io',
favoriteFruit: 'apple',
},
];
constructor() {}
getEmployees() { (2)
return this.employees;
}
getEmployee(id: number) { (3)
return this.employees[id];
}
}
1 | Array con los datos de prueba |
2 | Método que devuelve los datos de todos los empleados para la parte maestra |
3 | Método que devuelve los datos de un empleado para la parte de detalle |
2.3. Creación del componente de la parte maestra
Crearemos un componente encargado de gestionar los datos de los empleados y encargarse de su presentación. Se trata de la parte maestra. Más adelante, se podrá seleccionar un empleado de la lista para acceder a sus detalles.
ng g c components/employees
2.4. Actualización del archivo de rutas
Añadir una nueva ruta employees
a app.routes.ts
asociada al componente de los empleados.
...
{ path: 'employees', component: EmployeesComponent },
...
2.5. Actualización de la barra de menú
La lista de empleados será accesible desde el menú. Por tanto, hay que añadir una entrada en la barra de menús que conecte con la ruta employees
definida en el paso anterior.
...
<ul class="navbar-nav">
<li class="nav-item" routerLinkActive="active">
<a class="nav-link" routerLink="home">Home</a>
</li>
<li class="nav-item" routerLinkActive="active"> (1)
<a class="nav-link" routerLink="employees">Employees</a> (2)
</li>
<li class="nav-item" routerLinkActive="active">
<a class="nav-link" routerLink="about">About</a>
</li>
</ul>
...
1 | Nueva entrada en la lista de items de la barra de navegación |
2 | Conexión del elemento de menú con la ruta definida en app.routes.ts |
Con los cambios realizados la aplicación deberá estar como la de la figura. El router
se encarga de sustituir dinámicamente la presentación del componente seleccionado en la ruta. De eso se encarga el selector router-outlet
que incluimos en app.component.html
.
2.6. Implementación del controlador de la parte maestra
Los servicios son los encargados de tratar con los datos.
import { Component, OnInit } from '@angular/core';
import { EmployeesService } from '../../services/employees.service';
@Component({
selector: 'app-employees',
templateUrl: './employees.component.html',
styleUrls: ['./employees.component.css'],
})
export class EmployeesComponent implements OnInit {
employees: any[] = []; (1)
constructor(private employeesService: EmployeesService) { (2)
this.employees = employeesService.getEmployees(); (3)
}
ngOnInit(): void {}
}
1 | Variable de instancia para almacenar los datos recuperados por el servicio |
2 | Inyección del servicio para que esté disponible en la clase |
3 | Almacenamiento en la variable de instancia de los datos recuperados por el servicio |
2.7. Crear la vista de la parte maestra
<div class="row">
<div class="col">
<ul>
<li *ngFor="let employee of employees"> (1)
{{ employee.name.first }} {{ employee.name.last }} (2)
</li>
</ul>
</div>
</div>
1 | Iteración sobre la variable de instancia employees |
2 | Presentación de datos mediante interpolación |
Consultar más información sobre la interpolación en la documentación de Angular. |
El resultado será similar al de la figura.
2.8. Crear el componente para el detalle
Una vez creada la parte maestra pasamos al detalle, que mostrará información asociada al elemento seleccionado en el maestro. Comenzamos creando el componente
ng generate component components/employee
2.9. Actualización del archivo de rutas
Añadir una nueva ruta employee/:id
a app.routes.ts
asociada al componente del detalle de un empleado.
...
{ path: 'employee/:id', component: EmployeeComponent},
...
En el caso de este detalle no añadiremos ningún acceso directo desde el menú. El detalle tiene definida una ruta pero no se llega al detalle desde el menú, sino seleccionando el empleado en el listado |
2.10. Añadir al controlador maestro el acceso al detalle
El controlador maestro tiene que proporcionar un método que se encargue de ir al detalle. Ese método tomará como argumento el id
del empleado a mostrar y creará una ruta válida (definida en app.routes.ts
) a partir del id
.
...
export class EmployeesComponent implements OnInit {
employees: any[] = [];
constructor(
private employeesService: EmployeesService,
private router: Router (1)
) {
this.employees = employeesService.getEmployees();
}
showEmployee(id: number) { (2)
this.router.navigate(['employee', id]); (3)
}
ngOnInit(): void {}
}
1 | Inyección de un objeto router que permitirá la navegación a la página de detalle |
2 | Método que implementa el acceso al detalle |
3 | El método navigate toma como argumentos un array con la lista de literales y/o parámetros a añadir a la URL para confeccionar la ruta. |
2.11. Añadir a la vista del maestro el acceso al detalle
Se trata de encerrar entre un hipervínculo el valor del empleado mostrado en el maestro.
-
routerLink
le dará formato de hipervínculo. -
Se creará un evento
(click)
para llamar al método implementado en el controlador.
<div class="row">
<div class="col">
<ul>
<li *ngFor="let employee of employees">
<a routerLink="" (click)="showEmployee(employee.id)"> (1)
{{ employee.name.first }} {{ employee.name.last }}
</a>
</li>
</ul>
</div>
</div>
1 | routerLink hace que tenga estilo de hipervínculo. De la navegación se encarga el método showEmployee() , que toma como argumento el id del empleado seleccinado. |
El resultado será como el de la figura siguiente.
2.12. Implementar el controlador de la parte de detalle
El controlador del detalle tiene que tomar el valor del parámetro pasado en la ruta. Los parámetros se reciben en un array denominado params
. Como en el archivo app.routes.ts
se definió la ruta employee/:id
el parámetro se recibe en params['id']
.
import { Component, OnInit } from '@angular/core';
import { ActivatedRoute } from '@angular/router';
import { EmployeesService } from '../../services/employees.service';
@Component({
selector: 'app-employee',
templateUrl: './employee.component.html',
styleUrls: ['./employee.component.css'],
})
export class EmployeeComponent implements OnInit {
employee: any = {}; (1)
constructor(
private activatedRoute: ActivatedRoute, (2)
private employeesService: EmployeesService (3)
) {
this.activatedRoute.params.subscribe((params) => { (4)
this.employee = this.employeesService.getEmployee(params['id']); (5)
});
}
ngOnInit(): void {}
}
1 | Variable de instancia para almacenar los datos recuperados por el servicio |
2 | Inyección de la ruta para poder acceder al parámetro proporcionado |
3 | Inyección del servicio para recuperar los detalles del empleado |
4 | Obtener los parámetros. Se usa subscribe porque se trata de un Observable ya que se trata de una recepción asíncrona. |
2.13. Implementar la vista de la parte de detalle
La vista presenta los valores almacenados en la variable de instancia employee
.
<div class="row">
<div class="col">
<h2>{{ employee.name.first }} {{ employee.name.last }}</h2> (1)
<p>Company: {{ employee.company }}</p>
<p>Contact at: {{ employee.email }}</p>
</div>
</div>
1 | Uso de la interpolación para la presentación de datos |
El resultado de acceso a un detalle será similar a este.
3. Creación de un servicio sobre una API REST
Aquí veremos un ejemplo más real de la creación de un servicio. En nuestro caso, normalmente desarrollaremos servicios basados en APIs REST. Esto supone un modelo asíncrono en el que básicamente se realizan las peticiones y la respuesta no llega de forma inmediata. En su lugar, quedamos suscritos a la resolución de la petición.
Gran parte de los pasos son similares a los vistos en el apartado anterior de servicios basados en datos en memoria. Sin embargo, hay algunos ligeros cambios derivados de la naturaleza asíncrona de las peticiones HTTP. Aquí les dedicaremos especial atención.
JSONPlaceholder ofrece un servicio para prueba y prototipado rápido. Para este ejemplo de maestro detalle nos centraremos en dos endpoints:
-
https://jsonplaceholder.typicode.com/posts
obtiene una lista de posts. Lo usaremos para la parte maestro -
https://jsonplaceholder.typicode.com/posts/{id}/comments
obtiene la lista de comentarios de un post. Por ejemplo,https://jsonplaceholder.typicode.com/posts/1/comments
contiene los comentarios del post conid 1
.
3.1. Creación del servicio
ng generate service services/posts (1)
1 | Guardaremos los servicios en una carpeta services |
Añadir el servicio creado al array providers
en app.module.ts
...
providers: [EmployeesService, PostsService],
...
3.2. Implementación del servicio
import { Injectable } from '@angular/core';
import { HttpClient } from '@angular/common/http';
@Injectable({
providedIn: 'root',
})
export class PostsService {
constructor(private http: HttpClient) {} (1)
getQuery(query: string) { (2)
const url = `https://jsonplaceholder.typicode.com/${query}`;
return this.http.get(url);
}
getPosts() { (3)
return this.getQuery('posts');
}
getComments(id: string) { (4)
return this.getQuery(`posts/${id}/comments`);
}
}
1 | Inyección de un objeto HTTP que permita lanzar peticiones get sobre la API REST |
2 | Método auxiliar donde se recoge la parte común de la URL de los endpoints de la API. Se le pasa como parámetro lo que varía de un endpoint a otro |
3 | Método que recupera todos los posts |
4 | Método que recupera todos los comentarios de un post |
3.3. Creación del componente de la parte maestra
Crearemos un componente encargado de gestionar los datos de los posts y encargarse de su presentación. Se trata de la parte maestra. Más adelante, se podrá seleccionar un post de la lista para acceder a sus comentarios.
ng generate component components/posts
3.4. Actualización del archivo de rutas
Añadir una nueva ruta posts
a app.routes.ts asociada al componente de los posts.
export const ROUTES: Routes = [
...
{ path: 'posts', component: PostsComponent },
...
];
3.5. Actualización de la barra de menú
...
<li class="nav-item" routerLinkActive="active">
<a class="nav-link" routerLink="employees">Employees</a>
</li>
<li class="nav-item" routerLinkActive="active"> (1)
<a class="nav-link" routerLink="posts">Posts</a> (2)
</li>
<li class="nav-item" routerLinkActive="active">
<a class="nav-link" routerLink="about">About</a>
</li>
...
1 | Nueva entrada en la lista de items de la barra de navegación |
2 | Conexión del elemento de menú con la ruta definida en app.routes.ts |
Con los cambios realizados la aplicación deberá estar como la de la figura. El router
se encarga de sustituir dinámicamente la presentación del componente seleccionado en la ruta. De eso se encarga el selector router-outlet
que incluimos en app.component.html
.
3.6. Implementación del controlador de la parte maestra
Seguiremos estos pasos para implementar el controlador:
-
Inyectar el servicio en el constructor para tener acceso en la clase a los métodos implementados en el servicio.
-
Crear variables de instancia en el controlador para almacenar los datos con los que tratan los servicios.
-
Llamar al servicio en la implementación del constructor para que obtenga los datos y los cargue en las variables de instancia definidas para ello.
La vista accederá a las variables de instancia del controlador para presentar los datos. |
import { Component, OnInit } from '@angular/core';
import { PostsService } from '../../services/posts.service';
@Component({
selector: 'app-posts',
templateUrl: './posts.component.html',
styleUrls: ['./posts.component.css'],
})
export class PostsComponent implements OnInit {
posts: any[] = []; (1)
constructor(private postsService: PostsService) { (2)
this.postsService.getPosts().subscribe((data: any) => { (3)
this.posts = data; (4)
});
}
ngOnInit(): void {}
}
1 | Variable de instancia para almacenar los datos recuperados por el servicio |
2 | Inyección del servicio para que esté disponible en la clase |
3 | Suscripción al observable que devuelve los posts. La respuesta asíncrona se resuelve mediante una función de flecha que almacena en data los datos recuperados por el servicio |
4 | Almacenamiento en la variable de instancia de los datos recuperados por el servicio |
3.7. Crear la vista de la parte maestra
<div class="row">
<div class="col">
<ul>
<li *ngFor="let post of posts"> (1)
{{ post.title }} (2)
</li>
</ul>
</div>
</div>
1 | Iteración sobre la variable de instancia posts |
2 | Presentación de datos mediante interpolación |
El resultado será similar al de la figura.
3.9. Actualización del archivo de rutas
Añadir una nueva ruta posts/:id/comments
a app.routes.ts
asociada al componente del detalle de un post.
...
{ path: 'posts/:id/comments', component: CommentsComponent },
...
En el caso de este detalle no añadiremos ningún acceso directo desde el menú. El detalle tiene definida una ruta pero no se llega al detalle desde el menú, sino seleccionando el post en el listado. |
3.10. Añadir al controlador maestro el acceso al detalle
El controlador maestro tiene que proporcionar un método que se encargue de ir al detalle. Ese método tomará como argumento el id
del post al que mostrar los comentarios y creará una ruta válida (definida en app.routes.ts
) a partir del id.
import { Component, OnInit } from '@angular/core';
import { PostsService } from '../../services/posts.service';
import { Router } from '@angular/router';
@Component({
selector: 'app-posts',
templateUrl: './posts.component.html',
styleUrls: ['./posts.component.css'],
})
export class PostsComponent implements OnInit {
posts: any[] = [];
constructor(private postsService: PostsService,
private router: Router) { (1)
this.postsService.getPosts().subscribe((data: any) => {
this.posts = data;
});
}
showComments(id: string) { (2)
this.router.navigate(['posts', id, 'comments']); (3)
}
ngOnInit(): void {}
}
1 | Inyección de un objeto Router que permitirá la navegación a la página de detalle |
2 | Método que implementa el acceso al detalle |
3 | El método navigate toma como argumentos un array con la lista de literales y/o parámetros a añadir a la URL para confeccionar la ruta. |
3.11. Añadir a la vista del maestro el acceso al detalle
Se trata de encerrar entre un hipervínculo el valor del empleado mostrado en el maestro.
-
routerLink
le dará formato de hipervínculo. -
Se creará un evento
(click)
para llamar al método implementado en el controlador.
<div class="row">
<div class="col">
<ul>
<li *ngFor="let post of posts">
<a routerLink="" (click)="showComments(post.id)"> (1)
{{ post.title }}
</a>
</li>
</ul>
</div>
</div>
1 | routerLink hace que tenga estilo de hipervínculo. De la navegación se encarga el método showComments(). |
El resultado será como el de la figura siguiente.
3.12. Implementar el controlador de la parte de detalle
El controlador del detalle tiene que tomar el valor del parámetro pasado en la ruta. Los parámetros se reciben en un array denominado params. Como en el archivo app.routes.ts se definió la ruta posts/:id/comments
el parámetro se recibe en params['id'].
import { Component, OnInit } from '@angular/core';
import { ActivatedRoute } from '@angular/router';
import { PostsService } from '../../services/posts.service';
@Component({
selector: 'app-comments',
templateUrl: './comments.component.html',
styleUrls: ['./comments.component.css'],
})
export class CommentsComponent implements OnInit {
comments: any[] = []; (1)
constructor(
private activatedRoute: ActivatedRoute, (2)
private postsService: PostsService (3)
) {
this.activatedRoute.params.subscribe((params) => { (4)
this.postsService.getComments(params['id']).subscribe((data: any) => { (5)
this.comments = data; (6)
});
});
}
ngOnInit(): void {}
}
1 | Variable de instancia para almacenar los datos recuperados por el servicio |
2 | Inyección de la ruta para poder acceder al parámetro proporcionado <3 Inyección del servicio para recuperar los detalles del empleado |
3 | Obtener los parámetros. Se usa subscribe porque se trata de un Observable ya que se trata de una recepción asíncrona. |
4 | Obtener los comentarios. Se usa subscribe porque se trata de un Observable ya que se trata de una recepción asíncrona. |
5 | Asignación a una variable de instancia de los datos recibidos en la suscripción asíncrona |
3.13. Implementar la vista de la parte de detalle
La vista presenta los valores almacenados en la variable de instancia comments
.
<div class="row">
<div class="col">
<ul>
<li *ngFor="let comment of comments">
<h5>{{ comment.name }}</h5>
{{ comment.body }}
<small>
<span class="badge bg-success">
{{ comment.email }}
</span></small
>
</li>
</ul>
</div>
</div>
El resultado de acceso a un detalle será similar a este.