Le module ngRoute d'angularJS permet d'associer le chargement de contenus à des URLs prédéfinies.
Le module ngRoute n'est pas chargé par défaut avec AngularJS, il faut le faire explicitement :
<script src="https://ajax.googleapis.com/ajax/libs/angularjs/x.x.x/angular-route.min.js"></script>
Pour utiliser ngRoute dans un module, il faut déclarer la dépendance au module ngRoute dans la déclaration du module :
angular.module("sampleApp", ['ngRoute']);
La vue principale doit déclarer doit comporter une directive (une seule) ngView dans laquelle seront chargées les vues secondaires définies par le routage.
<div ng-view></div>
La page principale comporte également des liens qui seront définis par la suite dans la configuration du routeur :
<a href="#/route1">Route 1</a><br/> <a href="#/route2">Route 2</a><br/>
<!DOCTYPE html> <html ng-app="sampleApp"> <head> <meta charset="UTF-8"> <script src="http://ajax.googleapis.com/ajax/libs/angularjs/1.3.10/angular.min.js"></script> <script src="http://ajax.googleapis.com/ajax/libs/angularjs/1.3.10/angular-route.min.js"></script> </head> <body> <a href="#/route1">Route 1</a><br/> <a href="#/route2">Route 2</a><br/> <div ng-view></div> </body> </html>
Les vues secondaires sont les templates qui seront associés aux URLs /route1 et /route2 :
<h1>Route1</h1> <div ng-bind="rtCtrl1.content1"></div>
La configuration du routage se fait par l'appel de la méthode config du module, à laquelle on injecte le service $routeProvider
angular.module("sampleApp").config(['$routeProvider', function($routeProvider) { $routeProvider. when('/route1', { templateUrl: 'views/route1-template.html', controller: 'RouteController', controllerAs:'rtCtrl1' }); }]);
Si l'url #/route1 est demandée, le template views/route1-template.html est chargé, et le contrôleur RouteController sollicité.
Ajout du contrôleur gérant le template :
angular.module("sampleApp").controller("RouteController",[function(){ this.content1="Contenu du template de route1" }]);
Résultat obtenu :
angular.module("sampleApp").config(['$routeProvider', function($routeProvider) { $routeProvider. when('/route1', { templateUrl: 'views/route1-template.html', controller: 'RouteController', controllerAs: 'rtCtrl1' }). when('/route2/:nom', { templateUrl: 'views/route2-template.html', controller: 'RouteController', controllerAs: 'rtCtrl2' }); }]);
Le paramètre est passé à la l'url /route2/ par la variable :nom
Il faut ensuite modifier le contrôleur RouteController et lui injecter le service $routeParams pour qu'il puisse récupérer les paramètres passés :
angular.module("sampleApp").controller("RouteController",["$routeParams",function($routeParams){ this.content1="Contenu du template de route1"; this.params=$routeParams; }]);
Création du template correspondant à la route2, et utilisant la variable params déclarée dans le scope et récupérant le paramètre passé :
<h1>Route2</h1> <div>Bienvenue M. {{rtCtrl2.params.nom}}</div>
Modification du lien dans la vue principale :
... <body> <a href="#/route1">Route 1</a><br/> <a href="#/route2/Smith">Route 2</a><br/> <div ng-view></div> </body> ...
Résultat :
Il est également possible de passer des paramètres de manière plus classique dans l'URL :
... <body> <a href="#/route1">Route 1</a><br/> <a href="#/route2/Smith?prenom=John">Route 2</a><br/> <div ng-view></div> </body> ...
<h1>Route2</h1> <div>Bienvenue M. {{rtCtrl2.params.prenom}} {{rtCtrl2.params.nom}}</div>
Il est possible de définir la route par défaut : celle qui sera utilisée si toutes les autres routes ne trouvent pas leur correspondance :
angular.module("sampleApp").config(['$routeProvider', function($routeProvider) { $routeProvider. when('/route1', { templateUrl: 'views/route1-template.html', controller: 'RouteController', controllerAs: 'rtCtrl1' }). when('/route2/:nom', { templateUrl: 'views/route2-template.html', controller: 'RouteController', controllerAs: 'rtCtrl2' }).otherwise({ redirectTo: '/route1' }); }]);
Ajout d'un lien vers une route inexistante pour utiliser la route par défaut :
... <body> <a href="#/route1">Route 1</a><br/> <a href="#/route2/Smith">Route 2</a><br/> <a href="#/route3">Route inexistante</a><br/> <div ng-view></div> </body> ...
Le lien inexistant vers #/route3 conduit bien maintenant à la route par défaut route1
Pour masquer le # dans les urls utilisées, il est nécessaire d'injecter le service $locationProvider dans la configuration du routeur, pour activer le mode html5 :
angular.module("sampleApp").config(['$routeProvider','$locationProvider', function($routeProvider,$locationProvider) { $routeProvider. when('/route1', { templateUrl: 'views/route1-template.html', controller: 'RouteController', controllerAs: 'rtCtrl1' }). when('/route2/:nom', { templateUrl: 'views/route2-template.html', controller: 'RouteController', controllerAs: 'rtCtrl2' }).otherwise({ redirectTo: '/route1' }); if(window.history && window.history.pushState){ $locationProvider.html5Mode(true); } }]);
Dans le fichier par défaut v_main.html, ajouter la définition de la base des urls utilisées :
<head> <base href="/siteBaseURL/"/> ... </head>
Toutes les URLs utilisées (inclusions de js, CSS, templates) se feront à l'avenir à partir de cette localisation de base.
Il est ensuite nécessaire de modifier les liens définis vers les routes dans v_main.html :
... <body> <a href="route1">Route 1</a><br/> <a href="route2/Smith?prenom=John">Route 2</a><br/> <a href="route3">Route inexistante</a><br/> <div ng-view></div> </body> ...
Le seul problème reste le cas de l'accès direct aux routes par l'URL, ou le rafraîchissement de la page avec la touche F5 du clavier :
La réponse dans ce cas abouti à une erreur 404. Pour éviter cette réponse et maintenir un fonctionnement correct, il est nécessaire de modifier la configuration côté server :
RewriteEngine on # Pas de redirection pour les dossiers ou fichiers existants RewriteCond %{REQUEST_FILENAME} -f [OR] RewriteCond %{REQUEST_FILENAME} -d RewriteRule ^ - [L] # Redirige toutes les autres url vers v_main.html pour permettre le mode HTML5 RewriteRule ^ v_main.html [L]
Modifier le template associé à la route2 de façon à avoir une nouvelle variable dans le scope, dont le contenu est modifiable par une zone de texte :
<h1>Route2</h1> <div>Bienvenue M. {{rtCtrl2.params.nom}} {{rtCtrl2.params.prenom}}</div> <div><label>Entrez votre code : <input type="text" ng-model="code"></label></div>
La variable code saisie dans la zone de texte est perdue à chaque changement de vue, angularJS créant un nouveau scope à chaque chargement. Si on souhaite conserver la valeur du code saisie dans la vue correspondant à la route 2, il est nécessaire d'utiliser les closures javascript.
AngularJS fournit un outil à cet effet, il s'agit des factories :
... angular.module("sampleApp").factory("code", function() { return { value:"noCode" } })
La factory code retourne dans notre cas un objet possédant un membre value initialisé à “noCode”.
La factory code est ensuite injectée en tant que service dans le controller :
angular.module("sampleApp").controller("RouteController",["$routeParams","code",function($routeParams,code){ this.content1="Contenu du template de route1"; this.params=$routeParams; this.code=code; }]);
La variable code du $scope correspond à la factory code, il ne reste plus qu'à faire référence à code.value dans la vue :
<h1>Route2</h1> <div>Bienvenue M. {{rtCtrl2.params.nom}} {{rtCtrl2.params.prenom}}</div> <div><label>Entrez votre code : <input type="text" ng-model="rtCtrl2.code.value"></label></div>
En cas de changement de vue (passage de route2 à route1 puis retour à route2), les modifications opérées sur la variable code sont conservées (grâce à la closure créée par la factory).
D'autres directives existent, permettant de gérer le contenu et/ou les templates affichés, mais elles n'utilisent pas le routage :