|
|
|
## Bloque 3 – Navegación y estructura de aplicación (Angular moderno)
|
|
|
|
|
|
|
|
---
|
|
|
|
|
|
|
|
## Sesión 10 – Introducción al Router
|
|
|
|
|
|
|
|
### Introducción teórica
|
|
|
|
|
|
|
|
En Angular moderno, la navegación en aplicaciones SPA se gestiona sin módulos, utilizando una configuración centralizada de rutas basada en objetos `Routes`.
|
|
|
|
El Router sigue siendo el encargado de asociar rutas con vistas, pero ahora se apoya en **componentes standalone** y en la función `provideRouter`, lo que simplifica la arquitectura y reduce la complejidad inicial del proyecto.
|
|
|
|
|
|
|
|
---
|
|
|
|
|
|
|
|
### Ejemplo: configuración básica del Router en Panacea
|
|
|
|
|
|
|
|
Archivo `app.routes.ts`:
|
|
|
|
|
|
|
|
```ts
|
|
|
|
import { Routes } from '@angular/router';
|
|
|
|
|
|
|
|
export const routes: Routes = [
|
|
|
|
{
|
|
|
|
path: 'recursos',
|
|
|
|
loadComponent: () =>
|
|
|
|
import('./recursos/recursos.component')
|
|
|
|
.then(c => c.RecursosComponent)
|
|
|
|
},
|
|
|
|
{
|
|
|
|
path: 'activaciones',
|
|
|
|
loadComponent: () =>
|
|
|
|
import('./activaciones/activaciones.component')
|
|
|
|
.then(c => c.ActivacionesComponent)
|
|
|
|
}
|
|
|
|
];
|
|
|
|
```
|
|
|
|
|
|
|
|
Configuración del router en `main.ts`:
|
|
|
|
|
|
|
|
```ts
|
|
|
|
import { bootstrapApplication } from '@angular/platform-browser';
|
|
|
|
import { provideRouter } from '@angular/router';
|
|
|
|
import { AppComponent } from './app/app.component';
|
|
|
|
import { routes } from './app/app.routes';
|
|
|
|
|
|
|
|
bootstrapApplication(AppComponent, {
|
|
|
|
providers: [
|
|
|
|
provideRouter(routes)
|
|
|
|
]
|
|
|
|
});
|
|
|
|
```
|
|
|
|
|
|
|
|
---
|
|
|
|
|
|
|
|
## Sesión 11 – Navegación entre vistas
|
|
|
|
|
|
|
|
### Introducción teórica
|
|
|
|
|
|
|
|
En aplicaciones Angular sin módulos, el componente raíz actúa como contenedor de navegación.
|
|
|
|
El componente `<router-outlet>` indica dónde se renderiza la vista asociada a la ruta activa.
|
|
|
|
La navegación puede realizarse de forma declarativa mediante `routerLink` o de forma programática usando el servicio `Router`.
|
|
|
|
También se definen rutas por defecto y rutas comodín para mejorar la experiencia de navegación.
|
|
|
|
|
|
|
|
---
|
|
|
|
|
|
|
|
### Ejemplo: navegación entre Recursos y Activaciones
|
|
|
|
|
|
|
|
Archivo `app.component.ts`:
|
|
|
|
|
|
|
|
```ts
|
|
|
|
import { Component } from '@angular/core';
|
|
|
|
import { RouterOutlet, RouterLink } from '@angular/router';
|
|
|
|
|
|
|
|
@Component({
|
|
|
|
selector: 'app-root',
|
|
|
|
standalone: true,
|
|
|
|
imports: [RouterOutlet, RouterLink],
|
|
|
|
template: `
|
|
|
|
<nav>
|
|
|
|
<a routerLink="/recursos">Recursos</a>
|
|
|
|
<a routerLink="/activaciones">Activaciones</a>
|
|
|
|
</nav>
|
|
|
|
|
|
|
|
<router-outlet></router-outlet>
|
|
|
|
`
|
|
|
|
})
|
|
|
|
export class AppComponent {}
|
|
|
|
```
|
|
|
|
|
|
|
|
Rutas por defecto y wildcard en `app.routes.ts`:
|
|
|
|
|
|
|
|
```ts
|
|
|
|
export const routes: Routes = [
|
|
|
|
{ path: '', redirectTo: 'recursos', pathMatch: 'full' },
|
|
|
|
{
|
|
|
|
path: 'recursos',
|
|
|
|
loadComponent: () =>
|
|
|
|
import('./recursos/recursos.component')
|
|
|
|
.then(c => c.RecursosComponent)
|
|
|
|
},
|
|
|
|
{
|
|
|
|
path: 'activaciones',
|
|
|
|
loadComponent: () =>
|
|
|
|
import('./activaciones/activaciones.component')
|
|
|
|
.then(c => c.ActivacionesComponent)
|
|
|
|
},
|
|
|
|
{ path: '**', redirectTo: 'recursos' }
|
|
|
|
];
|
|
|
|
```
|
|
|
|
|
|
|
|
---
|
|
|
|
|
|
|
|
## Sesión 12 – Rutas con parámetros
|
|
|
|
|
|
|
|
### Introducción teórica
|
|
|
|
|
|
|
|
Las rutas con parámetros permiten adaptar una vista al contexto, como mostrar el detalle de un recurso sanitario concreto.
|
|
|
|
En Angular moderno, el servicio `ActivatedRoute` sigue siendo el mecanismo para acceder a parámetros de ruta y query params, manteniendo una API coherente con versiones anteriores.
|
|
|
|
|
|
|
|
---
|
|
|
|
|
|
|
|
### Ejemplo: detalle de recursos en Panacea
|
|
|
|
|
|
|
|
Ruta con parámetro:
|
|
|
|
|
|
|
|
```ts
|
|
|
|
{
|
|
|
|
path: 'recursos/:id',
|
|
|
|
loadComponent: () =>
|
|
|
|
import('./recursos/recursos.component')
|
|
|
|
.then(c => c.RecursosComponent)
|
|
|
|
}
|
|
|
|
```
|
|
|
|
|
|
|
|
Navegación con parámetro desde una vista:
|
|
|
|
|
|
|
|
```html
|
|
|
|
<a [routerLink]="['/recursos', recurso.id]">
|
|
|
|
Ver recurso
|
|
|
|
</a>
|
|
|
|
```
|
|
|
|
|
|
|
|
Lectura del parámetro en el componente:
|
|
|
|
|
|
|
|
```ts
|
|
|
|
import { ActivatedRoute } from '@angular/router';
|
|
|
|
|
|
|
|
constructor(private route: ActivatedRoute) {}
|
|
|
|
|
|
|
|
ngOnInit() {
|
|
|
|
const id = this.route.snapshot.paramMap.get('id');
|
|
|
|
}
|
|
|
|
```
|
|
|
|
|
|
|
|
Uso de query params para filtrar recursos:
|
|
|
|
|
|
|
|
```ts
|
|
|
|
this.router.navigate(['/recursos'], {
|
|
|
|
queryParams: { estado: 'activo' }
|
|
|
|
});
|
|
|
|
```
|
|
|
|
|
|
|
|
---
|
|
|
|
|
|
|
|
## Sesión 13 – Lazy loading y estructuración moderna
|
|
|
|
|
|
|
|
### Introducción teórica
|
|
|
|
|
|
|
|
En Angular moderno, el lazy loading se realiza directamente a nivel de componente mediante `loadComponent`, sin necesidad de módulos intermedios.
|
|
|
|
Este enfoque permite dividir la aplicación en áreas funcionales independientes, mejorando el rendimiento inicial y la mantenibilidad del código en aplicaciones grandes como Panacea.
|
|
|
|
|
|
|
|
---
|
|
|
|
|
|
|
|
### Ejemplo: carga diferida de vistas
|
|
|
|
|
|
|
|
```ts
|
|
|
|
{
|
|
|
|
path: 'activaciones',
|
|
|
|
loadComponent: () =>
|
|
|
|
import('./activaciones/activaciones.component')
|
|
|
|
.then(c => c.ActivacionesComponent)
|
|
|
|
}
|
|
|
|
```
|
|
|
|
|
|
|
|
Separación lógica dentro de una vista (ejemplo en Activaciones):
|
|
|
|
|
|
|
|
```ts
|
|
|
|
{
|
|
|
|
path: 'activaciones/:id',
|
|
|
|
loadComponent: () =>
|
|
|
|
import('./activaciones/activacion-detalle.component')
|
|
|
|
.then(c => c.ActivacionDetalleComponent)
|
|
|
|
}
|
|
|
|
```
|
|
|
|
|
|
|
|
Este enfoque permite estructurar vistas complejas sin introducir módulos.
|
|
|
|
|
|
|
|
---
|
|
|
|
|
|
|
|
## Sesión 14 – Guards y control de acceso
|
|
|
|
|
|
|
|
### Introducción teórica
|
|
|
|
|
|
|
|
Angular moderno introduce **guards funcionales**, que sustituyen a las clases tradicionales y reducen el código necesario.
|
|
|
|
Los guards permiten controlar el acceso a rutas y los resolvers facilitan la precarga de datos antes de mostrar una vista, manteniendo separada la lógica de navegación de la lógica de presentación.
|
|
|
|
|
|
|
|
---
|
|
|
|
|
|
|
|
### Ejemplo: guard funcional con login mockeado
|
|
|
|
|
|
|
|
Servicio de autenticación simulado:
|
|
|
|
|
|
|
|
```ts
|
|
|
|
import { Injectable } from '@angular/core';
|
|
|
|
|
|
|
|
@Injectable({ providedIn: 'root' })
|
|
|
|
export class AuthService {
|
|
|
|
private logged = true;
|
|
|
|
|
|
|
|
isLogged(): boolean {
|
|
|
|
return this.logged;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
```
|
|
|
|
|
|
|
|
Guard funcional `CanActivate`:
|
|
|
|
|
|
|
|
```ts
|
|
|
|
import { inject } from '@angular/core';
|
|
|
|
import { CanActivateFn, Router } from '@angular/router';
|
|
|
|
import { AuthService } from './auth.service';
|
|
|
|
|
|
|
|
export const authGuard: CanActivateFn = () => {
|
|
|
|
const auth = inject(AuthService);
|
|
|
|
const router = inject(Router);
|
|
|
|
|
|
|
|
if (auth.isLogged()) {
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
|
|
|
router.navigate(['/recursos']);
|
|
|
|
return false;
|
|
|
|
};
|
|
|
|
```
|
|
|
|
|
|
|
|
Uso del guard en rutas:
|
|
|
|
|
|
|
|
```ts
|
|
|
|
{
|
|
|
|
path: 'activaciones',
|
|
|
|
canActivate: [authGuard],
|
|
|
|
loadComponent: () =>
|
|
|
|
import('./activaciones/activaciones.component')
|
|
|
|
.then(c => c.ActivacionesComponent)
|
|
|
|
}
|
|
|
|
```
|
|
|
|
|
|
|
|
Introducción a un resolver funcional:
|
|
|
|
|
|
|
|
```ts
|
|
|
|
import { ResolveFn } from '@angular/router';
|
|
|
|
|
|
|
|
export const recursosResolver: ResolveFn<string[]> = () => {
|
|
|
|
return ['Recurso A', 'Recurso B'];
|
|
|
|
};
|
|
|
|
```
|
|
|
|
|
|
|
|
Aplicación del resolver:
|
|
|
|
|
|
|
|
```ts
|
|
|
|
{
|
|
|
|
path: 'recursos',
|
|
|
|
resolve: { datos: recursosResolver },
|
|
|
|
loadComponent: () =>
|
|
|
|
import('./recursos/recursos.component')
|
|
|
|
.then(c => c.RecursosComponent)
|
|
|
|
}
|
|
|
|
``` |