Routing delle applicazioni in Angular

R

Cos’è il routing

Il routing è una parte fondamentale di una qualunque applicazione front-end che si possa definire tale. Grazie ad esso è possibile manovrare un’applicazione in esecuzione attraverso la richiesta di specifici path nell’url. È il cuore di ogni “Single Page App” che si rispetti. Scopriamolo in Angular.

ROUting in angular 2+

In questo articolo andremo quindi a vedere come installarlo e come fare per utilizzarlo al meglio all’interno delle nostre applicazioni.
Tutto il codice di questo articolo potete trovarlo sulla relativa repo del mio profilo di github: angular-routing.

  1. Set-up del progetto con @Angular/cli
  2. Utilizziamo @angular/router

Set-up del progetto con @angular/cli

La fortuna di lavorare con Angular, e in generale con framework molto famosi, è quella di avere un ottimo supporto dall’enorme base di utenza ed è dalle ceneri di un altro framework (ember.js) che è nato Angular Cli: un’utility molto comoda per strutturare e mantenere app Angular nel modo più semplice possibile.
In base al vostro package manager preferito (dai un’occhiata anche a Yarn vs Npm), installiamo globalmente il pacchetto dando da terminale:

yarn global add @angular/cli

- or -

npm install -g @angular/cli

Terminata l’installazione, proseguiamo:

ng set --global packageManager=yarn (se si vuole usare yarn)
ng new angular-routing
cd angular-routing
ng serve

Nella prima linea impostiamo yarn per l’installazione (se lo si vuole usare), nella seconda con new si crea la cartella del progetto e tutta la sua struttura. Il cli “tirerà giù” automaticamente tutte le dipendenze necessarie per avviare correttamente un’applicazione Angular e tra queste ci sarà anche il nostro @angular/router. Alla terza linea ci spostiamo nella cartella del progetto per poterlo avviare attraverso il comando successivo: ng serve.
Verificate che tutto funziona visitando la pagina localhost:4200: il logo di Angular dovrebbe apparire nella pagina!
Per una lista esaustiva dei comandi che il cli mette a disposizione, trovate le doc ufficiali e per quanto riguarda ng serve lasciatelo anche proseguire in background, ad ogni modifica del codice che apporterete, lui ricaricherà automaticamente il browser.
Ma ora procediamo nella configurazione del router!

Info: Quando Angular 2 non era ancora in una release definitiva, si navigava in acque piuttosto sconosciute e molti erano gli esempi online. C’era chi utilizzava una propria configurazione, più o meno complessa, in webpack, chi semplicemente systemjs e via dicendo. Angular Cli non è sicuramente l’unico modo per creare un’applicazione in Angular, ma a meno che non avete particolari necessità (e anche in quel caso potreste modificare la configurazione webpack del cli) sconsiglio fortemente l’utilizzo di altre soluzioni.

Configuriamo @angular/router

Il modulo come abbiamo detto è pre-installato e lo trovate listato quindi nel package.json, ma va importato all’interno dell’app.module di Angular. Apriamo quindi il file angular-routing/src/app/app.module.ts e negli imports aggiungiamo il modulo del router:

import { RouterModule } from '@angular/router';
  
...

@NgModule({
  ...

  imports: [
    BrowserModule,
    RouterModule
  ],

  ...

Ora dobbiamo informare il router di come l’applicazione deve essere manovrata e per farlo abbiamo a disposizione due funzioni da poter chiamare:

forroot e forchild

La differenza sostanziale tra queste due funzioni è che mentre forRoot crea un modulo da importare che contiene tutte le direttive necessarie, le routes definite e il service stesso del router, forChild non importa il service invece il che è ottimo perché ci permette di definire le nostre routes in diversi punti dell’applicazione.
Sostanzialmente un “trucco” per ricordare facilmente questo dettaglio è quello di utilizzare il forRoot per le routes definite nell app.module (quindi il modulo root della nostra app) e forChild per gli altri moduli che andremo eventualmente a definire.

Creazione di 3 components

Prima di configurare le routes, abbiamo bisogno di due components tra cui navigare.

Info: Di questo si parla quando si parla di routing, di navigazione. Detto in soldoni, sono link che ti portano in qualche punto dell’applicazione senza ricaricare la pagina.

Generiamoli con il nostro cli in modo da non dover creare nessun file né importarli nel module: (ng generate component …)

ng g c Photos/Components/Photo1
ng g c Photos/Components/Photo2
ng g c Default

Notate la struttura delle directory. Ogni developer ne utilizza una che rispecchia più o meno le altre, la mia è quasi la stessa che viene mostrata nelle guidelines ufficiali di Angular ed è un approccio basato per modelli con all’interno cartelle per i component, un’altra per i service e via dicendo.

Ora, creiamo un file nella stessa directory di app.module.ts per definire le routes e chiamiamolo app.routes.ts. Qui dentro importiamo il tipo ‘Routes’ da @angular/router e i component creati sopra.

import { Routes } from '@angular/router';

import { DefaultComponent } from './default/default.component';
import { Photo1Component } from './photos/components/photo1/photo1.component';
import { Photo2Component } from './photos/components/photo2/photo2.component';

export const AppRoutes: Routes = [
  { path: '', component: DefaultComponent },
  { path: 'photo1', component: Photo1Component },
  { path: 'photo2', component: Photo2Component }
];

Il tutto dovrebbe essere molto semplice e lineare, con path si ha “la stringa nell’url” con cui il component definito a destra deve attivarsi.
Successivamente, importando le routes e chiamando la funzione forRoot nel modulo, avremo l’app.module così definito:

import { BrowserModule } from '@angular/platform-browser';
import { NgModule } from '@angular/core';
import { RouterModule } from '@angular/router';

import { AppComponent } from './app.component';
import { Photo1Component } from './photos/components/photo1/photo1.component';
import { Photo2Component } from './photos/components/photo2/photo2.component';
import { DefaultComponent } from './default/default.component'

import { AppRoutes } from './app.routes';

@NgModule({
  declarations: [
    AppComponent,
    Photo1Component,
    Photo2Component,
    DefaultComponent
  ],
  imports: [
    BrowserModule,
    RouterModule.forRoot(AppRoutes)
  ],
  providers: [],
  bootstrap: [AppComponent]
})
export class AppModule { }

Se avete ng serve e il tab del browser ancora attivo, ricaricate la pagina e verificate che nulla sia cambiato.

Quanto fatto finora non ha apportato alcuna modifica, come mai? Questo per due motivi:

  1. Non abbiamo definito un modo per raggiungere quei path (banalmente potremmo piazzarci un href)
  2. Non abbiamo comunicato ad angular dove vogliamo che questi component appaiano quando navighiamo tra di loro.

Iniziamo con l’aggiungere il menu e, quindi, i link che richiamino i component che abbiamo definito. Apriamo il file app.component.html e cancelliamo interamente il suo contenuto.
E’ possibile navigare tra component in due modi, vediamoli velocemente:

<nav>
  <a routerLink="">Default</a>
  <a routerLink="/photo1">Photo1 Component</a>
  <a routerLink="/photo2">Photo2 Component</a>
</nav>

La successiva è utilizzata in particolare per passare parametri:

<nav>
  <a [routerLink]="['']">Default</a>
  <a [routerLink]="['/photo1']">Photo1 Component</a>
  <a [routerLink]="['/photo2']">Photo2 Component</a>
</nav>

E per conoscere quale route è attiva in un dato momento, possiamo aggiungere la direttiva routerLinkActive che accetta come valore il nome della classe da settare all’elemento a cui appartiene.
Sostanzialmente questa direttiva non fa altro che fare un match tra le routes definite e il path presente nella barra degli indirizzi del browser. Ciò significa che se avremo un path del tipo / sarà attivato il link Default, se invece navighiamo in: /photo1 saranno attivati i link Default e Photo1 Component. Questo comportamento, più che un bug come pensava qualcuno, è semplicemente un meccanismo che tiene conto delle sotto navigazioni.
Per evitare questo comportamento basta utilizzare la proprietà in input [routerLinkActiveOptions]=”{ exact: true }” che offre un match esatto della route.
Cambiamo quindi il file app.component.html e in app.component.css aggiungiamo un po’ di stile che non fa mai male:

<nav>
 <a [routerLink]="['']" routerLinkActive="active" [routerLinkActiveOptions]="{ exact: true }">Default</a>
 <a [routerLink]="['/photo1']" routerLinkActive="active">Photo1 Component</a>
 <a [routerLink]="['/photo2']" routerLinkActive="active">Photo2 Component</a>
</nav>
a {
  text-decoration: none;
  color: #999;
}
a:hover {
  color: #333;
}
nav {
  margin: 2em;
  text-align: center;
  font-family: Arial;
}
.active {
  text-underline: none;
}
Router outlet

Cliccando i link però, ancora non riusciamo a visualizzare i component che vogliamo perché, come detto poco sopra, va comunicato ad Angular dove farli apparire attraverso il tag <router-outlet>.
Sostanzialmente ovunque verrà piazzato questo tag, si sostituirà l’html del component della rispettiva route definita nel file app.routes.ts.
L’unico component in comune tra tutti è l’app.component.ts, inseriamolo quindi li:

  ...
</nav>

<main>
  <router-outlet></router-outlet>
</main>
main {
  text-align: center;
  width: 50%;
}

Arrivati fin qui, abbiamo un primo semplicissimo esempio funzionante di routing, ma una introduzione che si rispetti deve anche mostrare come poter passare informazioni ai component attivati attraverso parametri nelle routes.

Parametri

Navigando da un component all’altro è possibile passargli delle informazioni dinamiche in modo che esso non venga attivato da un match esatto con il path. Chiarifichiamo la situazione direttamente con il codice. Apriamo il file app.routes.ts e modifichiamolo come segue:

...
export const AppRoutes: Routes = [
  { path: '', component: DefaultComponent },
  { path: 'photo1/:photoid', component: Photo1Component },
  { path: 'photo2', component: Photo2Component }
];

Notate il :photoid dopo il path photo1? Sta a specificare sostanzialmente che il component Photo1Component deve attivarsi ogni volta che si ha un match del tipo photo1/stringa con stringa che può contenere qualunque valore.
Come recuperiamo questo valore in quel determinato component? Niente di troppo complicato, editiamo il file angular-routing/src/app/photos/components/photo1/photo1.component.ts

import { Component, OnInit } from '@angular/core';
import { ActivatedRoute } from '@angular/router';

@Component({
  selector: 'app-photo1',
  templateUrl: './photo1.component.html',
  styleUrls: ['./photo1.component.css']
})
export class Photo1Component implements OnInit {

  photoid: string;
  constructor(private route: ActivatedRoute) { }
  
  ngOnInit() {
    this.route.params.subscribe((params) => this.photoid = params.photoid);
  }

}

Importiamo ActivatedRoute lo iniettiamo nel nostro componente attraverso il costruttore e richiamiamo la funzione subscribe sui parametri di una route. Attraverso una arrow function prendiamo i parametri ritornati dall’observable e assegniamo alla variabile di istanza photoid il valore che abbiamo ricevuto nel parametro. Dopodiché lo stampiamo nell’html del component (photo1.component.html).

<p>
 photo1 works!
 Il mio id è: {{photoid}}
</p>

Infine non ci rimane che impostare nel nostro nav un parametro da passare al nostro component:

<a [routerLink]="['/photo1', '001']" routerLinkActive="active">Photo1 Component</a>

Info: In un’applicazione reale questi link sono generati attraverso un ciclo iterativo. Ad esempio Angular effettua una chiamata HTTP all’endpoint http://mioserver.it/product e ottengo così la lista di prodotti con tutti i loro id ed iterativamente creo items con routerlink che puntano ai relativi id.

Se avete eseguito tutto correttamente, dando ng serve, dovreste poter cliccare su Photo1 Component e leggere photo1 works! Il mio id è: 001

A proposito di me

Giacomo Cerquone

Appassionato di informatica sin da piccolo, cinefilo accanito, amante di tè e dei posti freddi. Focalizzato in full-stack JavaScript developing, contributor di diversi progetti open source e amante delle tecnologie bleeding edge nell’ambito della programmazione.

Di Giacomo Cerquone

Gli articoli più letti

Articoli recenti

Commenti recenti