12 Rotte

Nelle applicazione costruite come single page application, per spostarci su un’altra pagina, faremo cambiare solo l’url, ma la pagina non verrà nuovamente ricaricata, limitando così notevolmente le connessioni al server.

Quello che vogliamo ottenere, tornando al nostro applicativo, sarà che impostando una rotta di questo tipo

http://localhost:4200/user

venga mostrata la nostra tabella degli utenti, mentre se apro per esempio

http://localhost:4200/users/1

mi dovrà mostrare i dettagli dell’ utente numero 1.

Per prima cosa dobbiamo definire le rotte in app.module importando RouterModule e il Routes

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

definiamo ora una costante di tipo Routes che sarà un array di oggetti, ogni oggetto conterrà le configurazioni degli url

const routes: Routes = [
 {

 }
];

La prima url che creiamo è la root dell’app, le proprietà che andremo ad inserire sono la path che conterrà il percorso dell’url, nel primo caso la root che essendo già specificata nella index.html dalla direttiva

<base href="/">

metteremo vuoto, la seconda è la pathMatch che indica se l’url deve corrispondere perfettamente ( full ) e la terza è il component che indica il componente scatenato da questa path e non va specificato tra apici

const routes: Routes = [
 {
  path: '',
  pathMatch: 'full',
  component: UsersComponent
 },
];

aggiungiamo gli altri oggetti per le url, cambiando la root con un reindirizzamento a users con la proprietà redirectTo

const routes: Routes = [
 {
  path: 'users',
  component: UsersComponent
 },
 {
  path: '',
  pathMatch: 'full',
  redirectTo: 'users'
 },
 {
  path: 'users/new',
  component: UserComponent
 }
];

Ora dobbiamo passare questo elenco di rotte al modulo RouterModule aggiungendo questo modulo per le rotte nelle dichiarazioni di imports del modulo della nostra app e anche negli altri moduli figli.

RouterModule.forRoot(routes)

Nell’ imports del modulo principale si utilizza il metodo di RouterModule che si chiama forRoot() che oltre a tutte le rotte passate, crea un’istanza, se non presente, del servizio Router.

Analogamente nei sottomoduli, si può importare il RouterModule utilizzando il metodo forChild(), che gestisce le rotte ed i provider, ma senza creare una nuova istanza del servizio Router.

Entrambi i metodi riceveranno come parametro l’oggetto contenente le configurazioni delle rotte, nel nostro caso la costante routes creata.

Installando l’applicazione in partenza con angular CLI, è consigliato specificare l’opzione routing=true

ng new myApp --routing=true

In questo modo saranno gestite in automatico le dipendenze dei moduli di routing e non dovremo preoccuparci di farlo noi manualmente e verrà creato un nuovo modulo apposito per il routing, app-routing.module.ts . Spostiamo quindi la nostra costante array di oggetti routes nel modulo, la differenza da prima è che nel modulo secondario dobbiamo dichiarare la direttiva exports

@NgModule({
 imports: [RouterModule.forRoot(routes)],
 exports: [RouterModule]
})

Ora dobbiamo specificare l’href del link caricato al click nel nostro nav.component.html, per far ciò in angular esiste una direttiva apposita che si chiama routerLink

<a class="nav-link" href="#" routerLink="">Home</a>

aggiungiamo gli altri <li> del menu e togliamo pure gli href

<li class="nav-item active">
 <a class="nav-link" routerLink="/">Home</a>
</li>
<li class="nav-item active">
 <a class="nav-link" routerLink="users">Utenti</a>
</li>
<li class="nav-item">
 <a class="nav-link" routerLink="users/new">Nuovo Utente</a>
</li>

Adesso che abbiamo reindirizzato correttamente le voci del menu, aggiungiamo nel componente principale app.module <router-outlet> una direttiva predefinita fornita da RouterModule che consente di indicare dove renderizzare un componente.

<lt-nav></lt-nav>
<div class="container">
 <h1>Sistema gestione utenti</h1>
 <div class="row">
  <div class="col-12">
   <router-outlet></router-outlet>
  </div>
 </div>
</div>

Reindirizzare un componente a seconda dell’ id

L’ intento è quello di mostrare i dati di un utente scelto in base all’id secondo l’url che sarà di questo tipo

http://localhost:4200/users/1/edit

Aggiungiamo nel nostro array di rotte un nuovo percorso a oggetto

{
path: 'users/:id/edit',
component: UsersDetailComponent
}

In questo caso :id è un place holder, ovvero un indicazione di prendere quello che viene passato tra users e edit.

Per catturare il numero dell’id dell’utente selezionato, utilizziamo un servizio di angular che si chiama activetedRoute, abbonandoci a questo servizio potremo leggere il parametro id.

La variabile user nel componente user-detail veniva settata ( set ) tramite un evento, adesso vogliamo che al caricamento dell’url /user/1/edit venga aperto il form e inizializzato un utente vuoto, per cui lo inizializziamo su ngOnInit

ngOnInit() {
 this.user = new User();
}

aggiungiamo ora nel costruttore l’iniezione del servizio activetedRoute

constructor( private userService: UserService, private route: ActivatedRoute) { }

se mandiamo in console la variabile route di tipo activetedRoute appena creata noteremo che conterrà un oggetto tra le cui proprietà avremo snapshot che a sua volta contiene la proprietà params che ricava l’id

constructor( private userService: UserService, private route: ActivatedRoute) {
 console.log(route.snapshot.params.id);
}

l’ id si trova anche nella proprietà route.params, quindi sottoscriviamo il servizio con subscribe() che appena attivata la rotta riceverà una varibile che passeremo come argomento di un’ arrow function

this.route.params.subscribe(
(rotta) => {

 }
)

Per avere accesso all’id della rotta selezionata, dobbiamo inizializzare l’utente passandogli il servizio userservice che lancia il metodo getUser() che aggiungiamo a user.service

this.route.params.subscribe(
(rotta) => {
  this.user = this.userService.getUser()
 }
)

getUser() riceve un id e restituisce l’utente corrispondente a quell’ id

getUser(id: number) {
 return this.users[id];
}

aggiungiamo nel ngOnInit la sottoscrizione a route.params che riceverà l’id dal metodo getUser()

ngOnInit() {
this.user = new User();

this.route.params.subscribe(
 (rotta) => {
  this.user = this.userService.getUser(rotta.id);
  }
 );
}

rotta.id deve contenere un dato di tipo number, per fare il cast esiste una forma abbreviata che consiste nell’ aggiungere un simbolo + davanti

this.user = this.userService.getUser(+rotta.id);

navigate

Ora non ci resta che associare al bottone modifica di ogni utente l’url da far caricare alla pagina.

Per fare questo, dobbiamo ragionare similmente all’ activatedRoute che attiva una rotta, ma useremo il metodo navigate() preso dal servizio Router. che inietteremo nel costruttore di user.component

constructor(private userService: UserService, private route: Router) { }

ricordarsi di importare Router anche in questo componente

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

Ora , sfruttando il metodo updateUser() precedentemente creato, aggiungiamo il metodo navigate() il quale prende come array di parametri su quale segmento di url (users) , il parametro da passare (l’id dell’utente) e un eventuale altro segmento (edit)

 this.route.navigate(['users', this.user.id, 'edit']);

a questo punto l’ indirizzo con l’id viene passato premendo il bottone modifica, l’unica cosa è che l’id passato non corrisponde a quello ricavato dal metodo getUser() che restituisce la posizione dell’array. Quindi l’utente 1 corrisponde all’indice 0, il 2 all’indice 1 e così via…

getUser(id: number) {
 return this.users[id];
}

Possiamo risolvere similmente a updateUser(), ma anzichè usare findIndex() usiamo il metodo find() che

getUser(id: number) {
 return this.users.find( user => user.id == id);
}

Mostrare il singolo utente

L’ultima cosa rimasta è mostrare i dati del singolo utente tramite un url di questo tipo

http://localhost:4200/users/1/show

creiamo un nuovo componente user-data e aggiungiamo una rotta in app-routing.module

{
 path: 'users/:id',
 component: UserDataComponent
}

iniettiamo nel costruttore di user-data la rotta ActivetedRoute e il servizio UserService e aggiungiamo nella classe una proprietà User

private User: User;
constructor(private route: ActivatedRoute, private service: UserService) { }

abboniamoci nel ngOnInit alla rotta passando i dati dal servizio a User. Il servizio userà il metodo getUser per recuperare l’id dell’utente

this.route.params.subscribe(p => this.User = this.service.getUser(+p.id) );

verifichiamo che i dati arrivino

{{User | json}}

facciamo qualche miglioria al codice per correggere qualche bug, come per esempio quando aggiungiamo un utente non veniamo reindirizzati alla home dopo il salvataggio.

In user-datail aggiungiamo router nel costruttore

constructor( private userService: UserService, private route: ActivatedRoute, private router: Router) { }

aggiungiamo un controllo che se non c’è id siamo in modifica quindi non serve leggere l’utente

this.route.params.subscribe(
(rotta) => {
 if (!rotta.id) {
  return;
 }
 this.user = this.userService.getUser(+rotta.id);
 }
);

in createUser() del service non viene creato un id per il nuovo utente

createUser(user: UserInterface) {
 user.id = this.users.length + 1;
 this.users.splice(0,0,user);
}

aggiungiamo la rotta col navigate nel saveUser() di user-detail

saveUser() {
 if (this.user.id > 0) {
  this.userService.updateUser(this.user);
 } else {
  this.userService.createUser(this.user);
 }
 this.router.navigate(['users']);
}

aggiungiamo un bottone INFO che visualizza i dati in user.component

<button class="btn btn-info" (click)="showUserDetail()">INFO</button>

aggiungiamo il metodo showUserDetail()

showUserDetail() {
this.route.navigate(['users', this.user.id]);
}

ora che compaiono i dati dei singoli utenti, mettiamoli in una tabella con un bottone torna indietro in user-data

<table class="table table-dark">
<tr>
<th>Name</th>
<td>{{User.nome}}</td>
</tr>
<tr>
<th>Cognome</th>
<td>{{User.cognome}}</td>
</tr>
<tr>
<th>Codice Fiscale</th>
<td>{{User.cf}}</td>
</tr>
<tr>
<th>Email</th>
<td>{{User.mail}}</td>
</tr>
<tr>
<th>Telefono</th>
<td>{{User.tel}}</td>
</tr>
<tr>
<th>Provincia</th>
<td>{{User.prov}}</td>
</tr>
<tr>
<th>Età</th>
<td>{{User.eta}}</td>
</tr>
</table>
<tfoot>
<tr>
<td colspan="2" class="text-center">
<button (click)="backToUsers()" class="btn btn-primary">INDIETRO</button>
</td>
</tr>
</tfoot>

ecco il metodo backToUsers() del click sul bottone

backToUsers() {
 this.router.navigate(['users']);
}