Nous souhaitons ajouter de nouvelles fonctionnalités à notre application:
-
Ajouter une vue Dashboard
-
Ajouter la possibilité de naviguer entre les vues Heroes et Dashboard
-
Quand un utilisateur clique sur le nom d’un héros dans l’une des vues, naviguer vers le héros sélectionné
-
Quand un utilisateur clique sur un deep link dans un email, ouvrir la vue détail de ce héros
1. Ajouter l’AppRoutingModule
Si vous avez indiqué l’option --routing
dans la commande ng new
au moment de la création du projet, cette étape est déjà réalisée, vous pouvez passer à l’étape suivante.
Dans le cas contraire, vous pouvez générer l' AppRoutingModule
grâce à la commande suivante:
ng generate module app-routing --flat --module=app
--flat ajoute le fichier directement dans src/app plutôt que dans son propre répertoire.--module=app indique au CLI de l’ajouter dans le tableau d' imports de l’AppModule.
|
Vous ne déclarerez généralement jamais de composant dans un module de routing, vous pouvez donc supprimer declarations
dans les metadata.
Nous n’utiliserons pas non plus le CommonModule, vous pouvez donc le supprimer du tableau des metadata et retirer sa ligne d’import.
Ajoutez maintenant un tableau exports
en-dessous d' imports
dans les metadata, et ajoutez-y RouterModule
.
C’est cet export qui nous permettra de rendre les routes disponibles pour êtres utilisées dans les composants d' AppModule
qui en auront besoin.
1.1. Ajouter des routes
Les Routes permettent au router de savoir quelle vue afficher quand un utilisateur clique sur un lien ou colle une URL dans la barre d’adresse du navigateur.
Une route Angular classique a deux propriétés:
-
path
: une string qui matche l’URL du navigateur. -
component
: le composant que le router devrait créer quand il navigue vers cette route.
Nous souhaitons naviguer vers le HeroesComponent
quand l’URL ressemble à quelque chose comme localhost:4200/heroes
.
Définissez votre première route dans une constante routes
de type Routes
, selon la syntaxe suivante:
const routes: Routes = [
{ path: 'my-path', component: MyComponent }
];
1.2. RouterModule.forRoot()
Vous devez d’abord initialiser le router pour qu’il écoute les changements d’URL du navigateur.
Ajoutez RouterModule
aux imports
dans les metadata, et configurez-le comme suit:
imports: [ RouterModule.forRoot(routes) ],
La méthode s’appelle forRoot parce que vous configurez le router au niveau racine de l’application.
La méthode forRoot() fournit les providers de service et les directives nécessaires pour le routing, et accomplit la navigation initiale en fonction de l’URL courante du navigateur.
|
2. Ajouter RouterOutlet
Ouvrez le template d' AppComponent
et remplacez-y <sw-heroes>
par <router-outlet>
.
Vous avez supprimé <sw-heroes>
parce que vous n’afficherez le HeroesComponent
que quand l’utilisateur naviguera vers son URL.
Le <router-outlet>
indique au router où afficher les vues liées au routing.
Le RouterOutlet est une des directives du router qui ont été rendues disponibles dans AppComponent parce que AppModule importe AppRoutingModule qui exporte RouterModule .
La commande ng generate que vous avez exécutée au début de ce tutoriel a ajouté cet import grâce au flag --module=app .
Si vous créiez app-routing.module.ts manuellement ou via un autre outil que le CLI, vous devriez importer AppRoutingModule dans app.module.ts
et l’ajouter au tableau imports du NgModule .
|
2.1. Testez-le
Le navigateur devrait se rafraîchir et l’application devrait toujours s’afficher, mais pas la liste des héros.
Si vous jetez un oeil à l’URL courante, vous devriez vous trouver sur localhost:4200
. Le chemin du HeroesComponent
est /heroes
.
Ajoutez /heroes
à la fin de l’URL, et la page devrait se rafraîchir pour cette fois afficher la liste des héros, comme souhaité.
3. Ajouter un lien de navigation (routerLink
)
Ca semble évident, mais les utilisateurs ne devraient pas avoir à coller un bout d’URL dans la barre d’adresse pour naviguer dans l’application. Ils devraient pouvoir naviguer via des liens cliquables.
Dans le template d' AppComponent
, ajoutez un élément <nav>
juste sous le titre, et à l’intérieur, un élément <a>
permettant d’accéder à la liste des héros, selon l’exemple suivant:
<nav>
<a routerLink="/heroes">Heroes</a>
</nav>
Un attribut routerLink
est assigné avec la valeur heroes
, la string que le router va matcher avec la route vers le HeroesComponent
.
Le routerLink
est le sélecteur de la directive RouterLink
qui lie les clics de l’utilisateur à la navigation. C’est une autre des directives publiques du RouterModule
.
Au rafraîchissement du navigateur, vous devriez désormais pouvoir cliquer sur le lien pour afficher la liste des héros.
3.1. Vous reprendrez bien un peu de CSS ?
Nous avons désormais une navigation fonctionnelle, mais pas super sexy… On va ajouter un peu de CSS dans app.component.scss
pour y remédier:
nav a {
padding: 5px 10px;
text-decoration: none;
margin-top: 10px;
display: inline-block;
background-color: #eee;
border-radius: 4px;
}
nav a:visited, a:link {
color: #607d8b;
}
nav a:hover {
color: #039be5;
background-color: #cfd8dc;
}
nav a.active {
color: #039be5;
}
4. Ajouter une vue dashboard
Le routing a plus de sens quand il y a plusieurs pages entre lesquelles naviguer. Pour l’instant, nous n’avons que la vue des héros.
Créez un DashboardComponent
avec le CLI Angular:
ng g c dashboard
4.1. La classe DashboardComponent
Nous allons commencer par implémenter les méthodes du dashboard.
-
Créer un attribut heroes, le typer comme tableau de héros, et l’initialiser avec un tableau vide.
-
Créer une méthode
getHeroes
qui va subscribe à la méthode duheroService
(qu’il faudra préalablement ajouter dans le constructor). -
Dans le subscribe, ajouter l’instruction suivante:
heroes ⇒ this.heroes = heroes.slice(1, 5)
. -
Appeler la méthode
getHeroes
dansngOnInit
.
4.2. Le template du dashboard
Copiez le code suivant dans le template du DashboardComponent
:
<h3>Top Heroes</h3>
<div class="grid grid-pad">
<a class="col-1-4">
<div class="module hero">
<h4><!-- hero name --></h4>
</div>
</a>
</div>
Mettez à jour ce code pour ajouter:
-
Sur
<a>
, un*ngFor
sur l’attributheroes
-
Dans
<h4>
, le nom du héros courant du*ngFor
4.3. Et pour finir, "un peu" de CSS
Ajoutez les règles de style suivants dans dashboard.component.scss
:
[class*='col-'] {
float: left;
padding-right: 20px;
padding-bottom: 20px;
}
[class*='col-']:last-of-type {
padding-right: 0;
}
a {
text-decoration: none;
}
*, *:after, *:before {
-webkit-box-sizing: border-box;
-moz-box-sizing: border-box;
box-sizing: border-box;
}
h3 {
text-align: center;
margin-bottom: 0;
}
h4 {
position: relative;
}
.grid {
margin: 0;
}
.col-1-4 {
width: 25%;
}
.module {
padding: 20px;
text-align: center;
color: #eee;
max-height: 120px;
min-width: 120px;
background-color: #607d8b;
border-radius: 2px;
}
.module:hover {
background-color: #eee;
cursor: pointer;
color: #607d8b;
}
.grid-pad {
padding: 10px 0;
}
.grid-pad > [class*='col-']:last-of-type {
padding-right: 20px;
}
@media (max-width: 600px) {
.module {
font-size: 10px;
max-height: 75px; }
}
@media (max-width: 1024px) {
.grid {
margin: 0;
}
.module {
min-width: 60px;
}
}
4.4. OK, ça fait quoi tout ça ?
Le template présente une grille de liens sur des noms de héros.
-
*ngFor
affiche autant de liens que d’éléments dans le tableauheroes
. -
Les liens sont stylisés en blocs colorés par le CSS.
-
Les liens ne vont pour l’instant nulle part, mais ça va bientôt changer.
La classe est similaire à celle du HeroesComponent
.
-
Elle définit un tableau
heroes
. -
Le constructeur injecte le
HeroService
dans un attribut privéheroService
. -
Le hook
ngOnInit()
appellegetHeroes
.
La méthode getHeroes
retourne la liste des héros tronquée des positions 1 à 5, soit seulement 4 "Top Heroes" (les 2ème, 3ème, 4ème et 5ème).
4.5. Ajouter la route du dashboard
Pour naviguer vers le dashboard, le router a besoin de la route appropriée.
Ajoutez une route dans les routes
d' AppRoutingModule
, qui lie le path dashboard
au DashboardComponent
.
4.6. Ajouter une route par défaut
Quand l’application démarre, le navigateur pointe vers la racine de l’application.
Ca ne matche aucune des routes définies, donc le navigateur ne navigue nulle part. L’espace dans <router-outlet>
est donc vide.
Pour que l’application navigue automatiquement vers le dashboard, ajoutez la route suivante dans les routes
d' AppRoutingModule
:
{ path: '', redirectTo: '/dashboard', pathMatch: 'full' },
Lorsque l’URL matche le path "vide", cette route redirige vers le path /dashboard
.
Au rafraîchissement du navigateur, le router charge le DashboardComponent
et la barre d’adresse affiche l’URL /dashboard
(si vous étiez précédemment situés sur localhost:4200
).
4.7. Ajouter un lien vers le dashboard
On souhaite que l’utilisateur puisse naviguer entre le DashboardComponent
et le HeroesComponent
en cliquant sur les liens de la zone de navigation.
Ajoutez un lien vers le dashboard dans AppComponent
, sobrement intitulé "Dashboard".
5. Naviguer vers les détails du héros
Le HeroDetailsComponent
affiche les détails du héros sélectionné. Pour l’instant, HeroDetailsComponent
est seulement visible en bas du HeroesComponent
.
L’utilisateur devrait pouvoir voir ces détails de 3 façons:
-
En cliquant sur un héros dans le dashboard.
-
En cliquant sur un héros dans la liste des héros.
-
En collant un "deep link" directement dans la barre d’adresse du navigateur, qui indique le héros à afficher.
Dans cette section, vous allez ajouter la navigation vers le HeroDetailsComponent
et le libérer du HeroesComponent
.
5.1. Supprimer hero details du HeroesComponent
Quand l’utilisateur clique sur un héros dans le HeroesComponent
, l’application devrait naviguer vers le HeroDetailComponent
.
La vue de la liste des héros ne devrait plus afficher les détails comme elle le fait maintenant.
Ouvrez le template d' HeroesComponent
et supprimez l’élément <sw-hero-detail>
.
5.2. Ajouter une route vers le détail
Une URL comme ~/heroes/:id
pourrait être une bonne route pour naviguer vers la vue de détail du héros dont l' id
est 11
.
Ouvrez AppRoutingModule
et ajoutez une route de path heroes/:id
pour le HeroDetailComponent
.
Les deux points (:) dans le path
indiquent que :id
est un placeholder pour un id de héros.
5.3. Les liens de héros du DashboardComponent
Pour l’instant, les liens vers les héros dans le DashboardComponent
ne font rien.
Maintenant que le router sait naviguer vers le HeroDetailComponent
, vous pouvez ajouter un routerLink
sur l’élément <a>
.
N’oubliez pas que les doubles accolades d’interpolation sont également disponibles dans routerLink:
<div routerLink="/my-route/{{foo.bar}}">/<div>
5.4. Les liens de héros du HeroesComponent
Actuellement, les héros de la listes sont des <li>
sur lesquels le clic est bindé à la méthode onSelect()
.
Retirez tous les attributs du <li>
excepté le *ngFor
, et ajoutez un <a>
autour du badge et du nom du héros.
Appliquez ensuite un routerLink
sur l’élément <a>
, comme vous l’avez fait dans le dashboard.
Remplacez le CSS du HeroesComponent
par celui-ci pour régler les soucis d’affichage apparus suite à ces changements:
.heroes {
margin: 0 0 2em 0;
list-style-type: none;
padding: 0;
width: 15em;
}
.heroes li {
position: relative;
cursor: pointer;
background-color: #EEE;
margin: .5em;
padding: .3em 0;
height: 1.6em;
border-radius: 4px;
}
.heroes li:hover {
color: #607D8B;
background-color: #DDD;
left: .1em;
}
.heroes a {
color: #888;
text-decoration: none;
position: relative;
display: block;
width: 250px;
}
.heroes a:hover {
color:#607D8B;
}
.heroes .badge {
display: inline-block;
font-size: small;
color: white;
padding: 0.8em 0.7em 0 0.7em;
background-color: #607D8B;
line-height: 1em;
position: relative;
left: -1px;
top: -4px;
height: 1.8em;
min-width: 16px;
text-align: right;
margin-right: .8em;
border-radius: 4px 0 0 4px;
}
5.4.1. Supprimer le code mort
Si le HeroesComponent
fonctionne toujours, la méthode onSelect()
et l’attribut selectedHero
ne sont plus utilisés.
Nettoyer le code non utilisé d’une application est une bonne pratique, et vous vous remercierez plus tard !
Supprimez donc ces deux éléments de la classe.
6. HeroDetailComponent
routable
Auparavant, l’attribut hero
du HeroDetailComponent
était défini par le HeroesComponent
.
Désormais, l’id du héros à afficher est indiqué dans l’URL au moment de la navigation (par exemple /hero/1
).
Vous allez donc devoir:
-
Récupérer la route courante dans le
HeroDetailComponent
. -
En extraire l'
id
. -
Récupérer le héros correspondant via le
HeroService
.
Injectez ActivatedRoute
, HeroService
et Location
dans le constructeur du HeroDetailComponent
dans 3 attributs privés.
Nous ferons ici une exception pour le nommage de l’attribut qui va instancier ActivatedRoute
, en le nommant simplement route
.
ActivatedRoute
contient les informations de la route de cette instance du HeroDetailComponent
. Nous serons intéressés par la partie qui contient les paramètres d’URL.
HeroService
va récupérer les données du héros depuis le serveur distant et ce composant l’utilisera pour afficher ses détails.
Location
est un service Angular pour interagir avec le navigateur. Vous l’utiliserez plus tard pour naviguer en arrière vers la vue qui vous a amené ici.
L’objet Location existe de base en JavaScript, de fait votre IDE ne va probablement pas vous proposer de l’importer dans le composant.
Vous devrez donc l’importer manuellement comme ceci: import { Location } from '@angular/common'
|
6.1. Extraire le paramètre id
Dans ngOnInit()
, appelez la méthode getHero()
que vous allez écrire dans un instant.
Définissez getHero()
comme suit:
-
Définissez une constante id prenant pour valeur
Number(this.route.snapshot.paramMap.get('id'));
-
Appelez la méthode
heroService.getHero(id)
que vous allez définir un peu plus tard. -
Effectuez un subscribe sur cette méthode du service, et dans ce subscribe assignez la valeur de retour
hero
à l’attributhero
du composant.
route.snapshot
est une image statique des informations de la route un peu après que le composant ait été créé.
paramMap
est un dictionnaire de paramètres de route extraits depuis l’URL. La clé "id"
retourne l’id du héros à récupérer.
Les paramètres de route sont toujours des strings. L’opérateur (+) convertit la string en nombre, ce qu’un id de héros devrait être.
6.2. Ajouter HeroService.getHero()
Ouvrez HeroService
et ajoutez-y cette méthode getHero()
.
getHero(id: number): Observable<Hero | undefined> {
// TODO: send the message _after_ fetching the hero
this.messageService.add(`HeroService: fetched hero id=${id}`);
return of(HEROES.find(hero => hero.id === id));
}
Notez les backquotes (`) qui définissent un template literal JavaScript pour embarquer l’id. Cette syntaxe permet généralement d’éviter de concaténer plusieurs strings via l’opérateur (+) et ainsi de faciliter la lecture. |
Comme getHeroes()
, getHero()
a une signature asynchrone, et renvoie un héros mocké dans un Observable.
Cela vous permettra de ré-implémenter getHero()
comme un vrai appel Http sans devoir changer le HeroDetailComponent
qui l’appelle.
6.2.1. Testez-le
Au rafraîchissement du navigateur, l’application devrait à nouveau fonctionner. Essayez d’accéder manuellement à l’URL localhost:4200/heroes/13
et vous devriez voir les détails de Chewbacca.
6.3. Trouver le chemin du retour
En cliquant sur le bouton "retour" du navigateur, vous pouvez revenir à la liste des héros ou au dashboard, selon la page dont vous veniez.
Il serait intéressant d’avoir un bouton qui puisse faire ça sur la vue HeroDetail
.
Ajoutez un bouton intitulé "go back" en bas du template du composant, et bindez son clic à la méthode goBack()
.
Ajoutez ensuite cette méthode goBack()
dans la classe du composant. Elle appelle la méthode this.location.back();
.
Et voilà pour le routing !
L’utilisation de this.location.back est rarement utilisée dans les applications, parce qu’elle peut ne pas renvoyer systématiquement vers la même page. Par exemple, si vous avez accédé au détail du héros depuis la liste, le bouton "go back" va vous ramener sur la liste. En revanche, si vous avez ouvert l’application directement sur la page de détail, le bouton "go back" va vous renvoyer soit sur le site précédent, soit nulle part si votre historique d’onglet est vide. On préférera donc utiliser this.router.navigate() pour rediriger explicitement l’utilisateur sur la route souhaitée.
|
Cette section est maintenant terminée, vous pouvez passer à la dernière étape: HTTP