Nell’era odierna dello sviluppo web, creare applicazioni dinamiche e interattive è ormai la norma. La creazione di funzionalità esclusive per determinati utenti, o disponibili al verificarsi di specifiche condizioni, può rivelarsi una sfida molto complessa.
Per questo motivo, Angular ci offre un sistema di routing basato su rotte, in inglese Routes, regole e componenti, con cui possiamo progettare facilmente le nostre applicazioni.
In questo articolo, vedremo come proteggere le nostre Route reindirizzando la navigazione altrove, utilizzando inoltre una nuova funzionalità offerta da Angular v18.
Ma prima di procedere, facciamo un breve ripasso della libreria Angular Router…
Angular Router: Guardie e Resolver
La libreria Angular Router ci permette di gestire la navigazione all’interno della nostra applicazione Angular, definendo una lista di Route.
Ogni Route è definita da una serie di informazioni, come il path per accedervi, il componente Angular da caricare, Route figlie e molto altro:
import { Route } from '@angular/router';
import { MyFeatureComponent, MyFeatureGuard } from './my-feature';
const routes: Route[] = [
{
path: 'my-feature',
component: MyFeatureComponent,
canActivate: [MyFeatureGuard],
data: {
id: "my-feature-id"
}
}
];
Code language: TypeScript (typescript)
Possiamo proteggere una o più Route, limitandone l’accesso o l’abbandono, in base a determinate condizioni, utilizzando delle apposite funzioni chiamate Guardie:
import { Route } from '@angular/router';
import { MyService } from './my-feature';
const myRoute: Route = [
path: 'my-feature',
canMatch: [() => inject(MyService).canMatch()],
canActivate: [() => inject(MyService).canActivate()],
canActivateChild: [() => inject(MyService).canActivateChild()],
canDeactivate: [() => inject(MyService).canDeactivate()],
];
Code language: TypeScript (typescript)
Articolo consigliato: La nuova funzione output di Angular
Esistono quattro tipi di Guardie Angular, ognuna con un ruolo differente:
canMatch
: viene utilizzata per verificare che una Route possa essere caricata. Possiamo definire più Route per un singolo percorso e utilizzare questa guardia per selezionarne una sola in base a determinate condizioni;canActivate
: viene utilizzata per determinare se un utente può attivare una determinata Route. Possiamo ad esempio utilizzarla per controllare l’accesso alle pagine riservate solo a determinati utenti;canActivateChild
: simile acanActivate
, ma controlla anche l’accesso alle Route figlie di una Route principale. Viene attivata ad ogni navigazione verso una Route figlia, anche se parte da un’altra Route figlia;canDeactivate
: viene utilizzata per determinare se un utente può uscire da una determinata Route. Possiamo ad esempio utilizzarla per chiedere conferma prima dell’abbandono di una pagina.
Nota: in origine, le Guardie erano implementate come servizi Injectable. In seguito all’introduzione della funzione inject(), è stato possibile definirle come funzioni. Di conseguenza, le Guardie Injectable sono ad oggi deprecate.
Inoltre, possiamo utilizzare delle funzioni chiamate Resolver per ricavare e/o preparare i dati per una Route:
import { Route } from '@angular/router';
import { MyService } from './my-feature';
const myRoute: Route = [
path: 'my-feature',
resolve: {
user: () => inject(MyService).getUserInfo(),
config: () => inject(MyService).getUserConfig()
}
];
Code language: TypeScript (typescript)
L’uso dei Resolver è un ottimo approccio per garantire la presenza dei dati prima di accedere ad una Route, e quindi eliminare la necessità di gestire dati mancanti durante la visualizzazione di una pagina.
Ora che abbiamo ripassato le basi, non ci resta altro che vedere come proteggere le nostre Route reindirizzando i nostri utenti altrove.
Utilizzare Guardie e Resolver per reindirizzare la navigazione
Come abbiamo visto, le Guardie Angular ci permettono di impedire l’accesso o l’abbandono ad una o più Route, bloccando la navigazione.
Tuttavia, per garantire un esperienza utente più fluida, è spesso preferibile reindirizzare la navigazione verso un altra Route.
Grazie alle Guardie, possiamo ottenere questo risultato molto facilmente, avviando una nuova navigazione prima di bloccare quella corrente:
import { inject } from '@angular/core';
import { Route, Router } from '@angular/router';
import { MyPage } from './pages/my-page';
const route: Route = {
path: 'my-page',
component: MyPage,
canActivate: [
() => {
const router = inject(Router);
router.navigate(['./my-other-page']);
return false;
},
],
};
Code language: TypeScript (typescript)
È possibile ottenere un risultato simile utilizzando i Resolver, avviando una nuova navigazione all’interno di queste:
import { Route, Router } from '@angular/router';
import { MyService } from './my-feature';
const myRoute: Route = [
path: 'my-feature',
resolve: {
user: () => {
const router = inject(Router);
router.navigate(['./my-other-page']);
return null;
}
}
];
Code language: TypeScript (typescript)
Reindirizzare con UrlTree
In alternativa, possiamo reindirizzare la navigazione facendo restituire a Guardie e Resolver un UrlTree
che rappresenta la nuova Route:
import { inject } from '@angular/core';
import { Route, Router, UrlTree } from '@angular/router';
import { MyPage } from './pages/my-page';
const route: Route = {
path: 'my-page',
component: MyPage,
canActivate: [
() => {
const router: Router = inject(Router);
const urlTree: UrlTree = router.parseUrl('./my-other-page');
return urlTree;
},
],
};
Code language: TypeScript (typescript)
Tuttavia, questa tecnica non permette di reindirizzare la navigazione utilizzando NavigationExtras
, come invece permette la tecnica precedente:
canActivate: [
() => {
const router = inject(Router);
router.navigate(['./my-other-page'], { skipLocationChange: true });
return false;
}
]
Code language: TypeScript (typescript)
Reindirizzare con RedirectCommand
Per ovviare a questa carenza, Angular v18 introduce una nuova classe RedirectCommand
in grado di accettare NavigationExtras
, con cui reindirizzare la navigazione in Guardie e Resolver:
import { inject } from '@angular/core';
import { RedirectCommand, Route, Router, UrlTree } from '@angular/router';
import { MyPage } from './pages/my-page';
const route: Route = {
path: 'my-page',
component: MyPage,
canActivate: [
() => {
const router: Router = inject(Router);
const urlTree: UrlTree = router.parseUrl('./my-other-page');
return new RedirectCommand(urlTree, { skipLocationChange: true });
},
],
};
Code language: TypeScript (typescript)
L’introduzione di questa nuova classe RedirectCommand
garantisce una grande manutenibilità per Guardie e Resolver.
Essendo stata progettata specificamente per questi casi d’uso, può essere infatti facilmente estesa per accogliere eventuali nuovi parametri in futuro.
Grazie per aver letto questo articolo 🙏
Mi piacerebbe avere qualche feedback quindi grazie in anticipo per qualsiasi commento. 👋
Infine, se ti è piaciuto davvero tanto, condividilo con la tua community. 👋😁