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.

Made with Slides.com