Sergio Hidalgo
Apasionado por la tecnología. Ninja Developer, FullStack, Adm Servidores, Profesor de Angular, Node, Phaser, Javascript. sergiohidalgocaceres@gmail.com https://www.facebook.com/groups/607163139705114/
Rutas
Las rutas nos permiten cargar componentes dinámicamente.
Las rutas usan la librería @angular/router y se crean dentro de un módulo.
Se debe importar la librería @angular/router
<!-- app.module.ts -->
import { Route, RouterModule } from '@angular/router'
...La interface Routes nos servirá para declarar rutas.
La clase RouterModule es la que gestionará las rutas.
Las rutas se definen dentro de un Array de tipo Route
<!-- app.module.ts -->
...
const rutas: Route[] = []
...La interface Routes tiene varias propiedades entre ellas están: "path" y "component"
<!-- app.module.ts -->
...
import { HomeComponent } from './home.component'
...
const rutas: Route[] = [
{path: "", component: HomeComponent}
]
..."path" define la ruta. La ruta nunca empieza con "/".
"component" indica la clase del componente que será instanciado. Miren que "component" no recibe un string. Recibe una instancia.
Para que las rutas sean gestionadas, debe importarse el módulo "RouterModule" y usar su método "forRoot".
<!-- app.module.ts -->
...
const rutas: Route[] = [
{ path: "", component: HomeComponent }
]
...
@NgModule({
...
imports: [
...
RouterModule.forRoot(rutas)
...
],
...
})No olvidar que una cosa es importar la definición de un componente usando:
import { HomeComponent } from './home.component'Y otra cosa es declararlo en un módulo:
NgModule({
declarations: [
HomeComponent
]
})
Se pueden importar muchas veces las definiciones de un componente a través de "import",
pero solo se puede declarar un componente (o directiva) una sola vez y desde únicamente un módulo.
Los componentes se cargan dinámicamente y es posible usar un selector específico (de un componente)
Lo que se usa es la etiqueta <router-outlet>
Inicialmente la vamos a usar dentro del archivo "app.component.html" porque es el componente que primero se instancia.
<!-- app.component.html -->
...
<router-outlet></router-outlet>
...routerLink
Nos permite crear enlaces (links) de rutas para cargar otros componentes
Suponiendo que tenemos las siguientes rutas:
const rutas: Route[] = [
{ path: "", component: HomeComponent },
{ path: "libros", component: LibroListadoComponent },
{ path: "libros/edicion", component: LibroEdicionComponent }
]HomeComponent
Se cargará cuando la ruta sea http://localhost:4200
LibroListadoComponent
Se cargará cuando la ruta sea http://localhost:4200/libros
LibroEdicionComponent
Se cargará cuando la ruta sea http://localhost:4200/libros/edicion
Podemos crear enlaces a las rutas usando el atributo "href"
<a href="/">Home</a>
<a href="/libros">Listado de libros</a>
<a href="/libros/edicion">Edición de libro</a>Los componentes se cargan efectivamente según la ruta.
Pero la página se recarga, lo cual va en contra de la filosofía de Angular (cargar dinámicamente sin recargar)
En lugar del atributo "href" se usa el atributo "routerLink"
<a routerLink="/">Home</a>
<a routerLink="/libros">Listado de libros</a>
<a routerLink="/libros/edicion">Edición de libro</a>Ahora los componentes no recargan la página.
"routerLink" es una propiedad definida por Angular.
También se la puede usar con corchetes.
<a [routerLink]="'/'">Home</a>
<a [routerLink]="'/libros'">Listado de libros</a>
<a [routerLink]="'/libros/edicion'">Edición de libro</a>En el componente "libros" agreguemos un enlace en el html.
<!-- libro.component.html -->
...
<a routerLink="libros">Listado</a>
...Esto da error. La ruta asignada al "routerLink" es relativa a la ruta actual, o sea a la ruta "http://localhost:4200/libros".
La ruta asignada se traduce en "http://localhost:4200/libros/libros", la cual no existe.
La forma correcta es:
<!-- libro.component.html -->
...
<a routerLink="/libros">Listado</a>
...La ruta asignada al "routerLink" es ahora absoluta.
La expresión a evaluar en "routerLink" puede también ser expresada como un arreglo.
<a [routerLink]="['/']">Home</a>
<a [routerLink]="['/listado']">Listado de libros</a>
<a [routerLink]="['/listado/edicion']">Edición de libro</a>routerLinkActive
Nos permite asignar una clase a un enlace según la ruta seleccionada
A cada enlace se le agrega la clase "activo" si la ruta concuerda con el valor de "routerLink"
<a [routerLink]="['/']"
routerLinkActive="activo">Home</a>
<a [routerLink]="['/listado']"
routerLinkActive="activo">Listado de libros</a>
<a [routerLink]="['/listado/edicion']"
routerLinkActive="activo">Edición de libro</a>.activo, .activo:link, .activo:visited{
color: red
}
Notarán que no se cumple. Las clases "activo" se añaden a veces solo a un enlace y a veces a más de uno.
Esto se corrige con "routerLinkActiveOptions".
<a [routerLink]="['/']"
routerLinkActive="activo"
[routerLinkActiveOptions]="{exact: true}">
Home
</a>
<a [routerLink]="['/listado']"
routerLinkActive="activo"
[routerLinkActiveOptions]="{exact: true}">
Listado de libros
</a>
<a [routerLink]="['/listado/edicion']"
routerLinkActive="activo"
[routerLinkActiveOptions]="{exact: true}">
Edición de libro
</a>Navigate
Es un método que nos permite crear y navegar en rutas desde el typescript
El método "navigate" pertenece al servicio "Router".
El servicio debe inyectarse al typescript del componente.
...
import { Router } from "@angular/router"
...
export class LibroListado {
...
constructor(private ruteador: Router) {}
navegar(){
this.ruteador.navigate(["/listado", "edicion"])
}
...
}
El método "navigate" puede recibir varios parámetros. En este caso solo recibe uno.
Este parámetro es un arreglo. Con este arreglo el método "navigate" crear la siguiente ruta:
http://localhost:4200/listado/edicion
...
navegar(){
this.ruteador.navigate(["/listado", "edicion"])
}
...
Si le quitamos el "/" en el primer elemento del arreglo.
El resultado será el mismo porque por defecto las rutas que crea este método, son absolutas.
...
navegar(){
this.ruteador.navigate(["listado", "edicion"])
}
...
relativeTo en Navigate
Nos permite navegar de forma relativa
La opción "relativeTo" nos permite indicar que la ruta será relativa a la ruta actual o activa.
Para obtener la ruta actual, debemos importar e inyectar el servicio "ActivatedRoute"
...
import { ActivatedRoute } from "@angular/router"
...
export class LibroListadoComponent {
...
constructor(private ruteador: Router,
private rutaActiva: ActivatedRoute){}
navegar(){
this.ruteador.navigate(["edicion"], { relativeTo: this.rutaActiva })
}
}Parámetros en rutas
Modifiquemos la ruta que llama al componente "LibroEdicionComponent"
const rutas: Route[] = [
{ path: "", component: HomeComponent },
{ path: "libros", component: LibroListadoComponent },
{ path: "libros/:id/edicion", component: LibroEdicionComponent }
]Todos los parámetros tienen a ":" como prefijo.
Arriba hemos declarado un parámetro llamado ":id"
Debemos modificar el enlace a la ruta que lleva al componente "ListadoEdicionComponent"
...
<a [routerLink]="['listado', 20, 'edicion']"
routerLinkActive="activo"
[routerLinkActiveOptions]="{exact: true}">
Edición de libro
</a>También hay que modificar los parámetros en el método "navigate".
...
navegar(){
this.ruteador.navigate(["listado", 20, "edicion"])
}
...
Lectura de parámetros
Para leer parámetros se necesita acceder a la foto instantánea (snapshot) de la ruta actual.
Para ello usamos nuevamente "ActivatedRoute"
...
id: number
constructor(private rutaActiva: ActivatedRoute){}
ngOnInit(){
this.id = this.rutaActiva.snapshot.params.id
}
...
Las suscripciones
Las suscripciones nos permiten monitorear cualquier cambio en los parámetros
Supongamos que el componente "ListadoEdicionComponent" agregamos un botón con un método "navegar10".
...
<button (click)="navegar10()">Ir al registro 10</button>
... id: number
constructor(private rutaActiva: ActivatedRoute,
private ruteador: Router){}
ngOnInit(){
this.id = this.rutaActiva.snapshot.params.id
}
navegar10(){
this.ruteador.navigate(["listado", 10, "edicion"])
}Como resultado veremos que la ruta en el navegador cambia, pero no cambia el valor mostrado en el html.
Esto se debe a que el componente "LibroEdicionComponent" ya estaba cargado y no se puede volver a cargar el mismo componente si ya está cargado.
Por lo tanto el método "ngOnInit" solo se ejecuta una vez.
ngOnInit(){
this.id = this.rutaActiva.snapshot.params.id
}
Para solucionar el problema usaremos las suscripciones.
Una suscripción no es más que un método que se ejecuta cuando hay cambios en los parámetros.
Para usar suscripciones, debe importarse la clase "Suscription"
import { Subscription } from "rxjs/Subscription"La ruta activa tiene un objeto "params".
Nos vamos a suscribir a este objeto "params".
Para suscribirnos usamos el método "subscribe"
suscripcion: Subscription
ngOnInit() {
this.id = this.rutaActiva.snapshot.params.id
this.suscripcion = this.rutaActiva.params.subscribe(
(parametros: Params) => {
this.id = parametros["id"];
}
);
}Todas las suscripciones deben ser eliminadas al momento que el componente se destruye, o sea a través del método "ngOnDestroy" usando el método "unsubscribe"
suscripcion: Subscription
...
ngOnDestroy(){
this.suscripcion.unsubscribe()
}Lectura de parámetros de tipo QueryString
La lectura de parámetros de tipo querystring se hace casi de la misma forma que los parámetros de url.
Supongamos que la ruta es http://localhost:4200/listado?rol=admin
ngOnInit(){
this.rol = this.rutaActiva.snapshot.queryParams.rol
}
La suscripción es similar a la suscripción de los parámetros url.
ngOnInit(){
this.rol = this.rutaActiva.snapshot.queryParams.rol
this.suscripcion = this.rutaActiva.queryParams
.subscribe(
(parametros: Params) => {
this.rol = parametros["rol"];
}
)
}
ngOnDestroy(){
this.suscripcion.unsubscribe()
}
Lectura de parámetros de tipo fragmento
La lectura de parámetros de tipo fragmento también se hace casi de la misma forma que los parámetros de url.
Supongamos que la ruta es
http://localhost:4200/listado?rol=admin#libre
ngOnInit(){
this.fragmento = this.rutaActiva.snapshot.fragment
}
La suscripción es similar a la suscripción de los parámetros url.
ngOnInit(){
this.fragmento = this.rutaActiva.snapshot.fragment
this.suscripcion = this.rutaActiva.fragment
.subscribe(
(fragmento: string) => {
this.fragmento = fragmento;
}
)
}
ngOnDestroy(){
this.suscripcion.unsubscribe()
}
Tarea 1
- Crear una app que tenga las secciones home y listado de vehículos en un menú.
- Los vehículos tendrán las siguientes características: marca, modelo, año de fabricación, tipo (sedán, camioneta, camión).
- Crear los componentes: Home, Listado, Edición y Nuevo.
- Crear las rutas para todos ellos.
- Asuman una data inicial de vehículos declarada en un servicio.
Rutas hijas
Las rutas hija siempre tienen un padre.
Aquí todas las rutas son padres.
const rutas: Route[] = [
{ path: "", component: HomeComponent },
{ path: "libros", component: LibroListadoComponent },
{ path: "libros/:id/edicion", component: LibroEdicionComponent }
]const rutas: Route[] = [
{ path: "", component: HomeComponent },
{ path: "libros", children:[
{ path: "", component: LibroListadoComponent }
{ path: ":id/edicion", component: LibroEdicionComponent }
]}
]Primera forma: solo varían las rutas
const rutas: Route[] = [
{ path: "", component: HomeComponent },
{ path: "libros", component: LibroListadoComponent, children:[
{ path: ":id/edicion", component: LibroEdicionComponent }
]}
]Segunda forma: varían las rutas y también el html del padre.
<!-- libro.component.html -->
...
<router-outlet></router-outlet>
...Rutas inexistentes
const rutas: Route[] = [
{ path: "", component: HomeComponent },
{ path: "libros", component: LibroListadoComponent, children:[
{ path: ":id/edicion", component: LibroEdicionComponent }
]},
{ path: "no-ruta", component: RutaNoEncontrada }
]Si se ingresa una ruta inexistente como "no-ruta", la podemos mapear como una ruta.
const rutas: Route[] = [
{ path: "", component: HomeComponent },
{ path: "libros", component: LibroListadoComponent, children:[
{ path: ":id/edicion", component: LibroEdicionComponent }
]},
{ path: "no-ruta", component: RutaNoEncontrada },
{ path: "**", redirectTo: "no-ruta" }
]El problema obvio es que no podemos mapear las millones de posibilidades. Para solucionar el problema usaremos un comodín.
Esa última ruta es una ruta comodín y siempre debe ir al final de las rutas. La ruta comodín significa "cualquier ruta".
Delegando rutas
Lo mejor es siempre delegar la gestión de rutas a un módulo menor
Este será el módulo de rutas delegado
import { NgModule } from "@angular/core";
import { Route, RouterModule } from "@angular/router";
import { HomeComponent } from './home/home.component';
import { LibroEdicionComponent } from './libro-edicion/libro-edicion.component';
import { LibroListadoComponent } from './libro-listado/libro-listado.component';
import { NoEncontradoComponent } from './no-encontrado/no-encontrado.component';
const rutas: Route[] = [
{ path: "", component: HomeComponent },
{ path: "libros", component: LibroListadoComponent, children:[
{ path: ":id/edicion", component: LibroEdicionComponent }
]},
{ path: "no-ruta", component: NoEncontradoComponent },
{ path: "**", redirectTo: "no-ruta" }
]
@NgModule({
imports: [
RouterModule.forRoot(rutas)
],
exports: [
RouterModule
]
})
export class AppRouting { }Este será el módulo de rutas delegado
import { AppRouting } from "./app.routing.module"
@NgModule({
...
imports: [
BrowserModule,
FormsModule,
AppRouting
]
...
})
export class AppModule { }Tarea 2
- Modificar la app creada en la Tarea 1 para usar rutas hijas y delegación de rutas.
By Sergio Hidalgo
Rutas: ¿Qué son? ¿Cómo las configuramos? ¿Cómo leemos parámetros url, querystring y fragmento? ¿Qué pasa con las rutas inexistentes? ¿Cómo delegamos la gestión de rutas?
Apasionado por la tecnología. Ninja Developer, FullStack, Adm Servidores, Profesor de Angular, Node, Phaser, Javascript. sergiohidalgocaceres@gmail.com https://www.facebook.com/groups/607163139705114/