Blog

AngularUI Router, le ngRoute en mieux

Lors de la création d’application web monopage (single-page application) avec AngularJS, nous voulons que la navigation sur le site soit semblable à celle d’un site classique sans avoir à recharger notre page. AngularJS fournit ngRoute qui permet de réaliser ce routage.
UI-Router propose une vision différente afin de gérer la navigation sur notre application.

Dans tous les états

Le framework de routing UI-Router offre une approche différente par rapport à ngRoute. Celui-ci permet de modifier les vues de votre application en fonction de l’état de celle-ci et non pas de l’URL.

Le gestionnaire d’état $stateProvider d’UI-Router est similaire au routeur d’AngularJS mais se concentre uniquement sur l’état de l’application. Un état peut être une URL, mais peut aussi être un moment ou une étape lors de la visite d’une page.

Un état correspond à un endroit de notre application en terme d’interface utilisateur et de navigation et décrit l’apparence et la logique de l’interface utilisateur à cet endroit.

Avantages

ngRoute est un bon gestionnaire de navigation, mais UI Router va plus loin :

  • Les états qui offrent des choses en commun peuvent être hiérarchisés à travers une relation parent / enfant nommée aussi états imbriqués
  • La possibilité de nommer ses vues afin d’en avoir plusieurs sur un même template : ce sont les vues multiples
  • Grâce à la directive ui-sref, nos URLs sont générées dynamiquement et peuvent être changées facilement à un seul endroit de notre application
  • La possibilité de passer des paramètres à nos états facilement avec $stateParams

Mise en place

La manière la plus simple de l’intégrer à notre projet est de télécharger les sources et de l’inclure dans notre index.html.

J’utilise personnellement Browserify (voir notre article sur le sujet) dans mes projets et installe donc ui-router via npm.

var modules = [require('angular-ui-router')];
// Si on n'utilise pas Browserify
// var modules = ['ui-router'];
var app = angular.module('myApp', modules);


    

La directive ui-view remplace le ng-view de ngRoute, les templates que nous définirons dans nos états seront automatiquement insérés dans la directive du template de leur état parent. Si on est dans un état qui n’a pas de parent, alors son template parent sera index.html.

N’oubliez pas le ui-view, sinon il ne se passera rien.

<

h2>C’est parti

Pour définir un état, nous allons déclarer une route à l’aide de la méthode .config comme on le ferait avec $routeProvider, mais à la place, nous allons utiliser $stateProvider

app.config(['$stateProvider', router]);

function router($stateProvider) {
    var mystate = {
        name = 'mystate',
        templateUrl: 'state.html'
    };

    $stateProvider
        .state(mystate);
};

On crée un petit fichier html pour notre état :


Mon État

Activer un état

Pour activer notre état, nous avons 3 méthodes :

  • Utiliser $state.go(), ici $state.go('mystate');
  • Naviguer vers l’URL associée à notre état.
  • Cliquer sur un lien qui contient la directive ui-sref.

La directive ui-sref permet de lier un lien () à un état.
Si notre état possède une url, alors la directive va automatiquement générer le href de notre balise
.

Mon état

Un état plein de possibilités

Définir un template

Pour définir un template, il existe plusieurs possibilités :

  • template pour définir directement le contenu de votre template
  • templateUrl pour charger un fichier HTML
  • templateProvider pour générer dynamiquement un template à l’aide d’une function

Pour en savoir plus sur la configuration du template d’un état : Template

Ajouter un contrôleur

Il existe plusieurs méthodes afin d’assigner un contrôleur au template d’un état (j’utilise la syntaxe « Controller as », mais il est possible d’utiliser le classique $scope).

Votre contrôleur ne sera pas intancié si vous n’avez pas défini de template.

var mystate = {
    template: '{{ Details.name }}',
    controller: function() {
        this.name = 'Mon État';
    },
    controllerAs: 'Details'
};

Si votre contrôleur est déjà défini :

var mystate = {
    template: '{{ Details.name }}',
    controllerAs: 'DetailsCtrl as Details'
};

Votre contrôleur sera directement accessible via Details dans votre template.

Vous voulez en savoir plus : la doc sur ce sujet.

La résolution des dépendances

Le resolve, comme son nom l’indique, permet de résoudre des dépendances et de les injecter dans notre contrôleur. Dans le cas où notre resolve est une promesse, elle sera résolue avant l’instanciation du contrôleur ce qui permet, par exemple, de vérifier si l’utilisateur est connecté avant d’effectuer la transition d’état.

var mystate = {
    template: '{{ Details.name }}',
    resolve: {
        name: function() {
            return 'Mon État';
        },
        httpPromise: function($http) {
            return $http.get('/users/isLoggedIn');
        }
    },
    controller: function(name, httpPromise) {
        this.name = name;
        this.loggedIn = httpPromise;
        if (!this.loggedIn) {
            $state.go('signin');
        }
    },
    controllerAs: 'Details'
};

Les URLs

La forme simple de définition d’une URL pour notre état est la suivante :

var mystate = {
    url: '/state',
    template: '

Mon État

' };

Lorsque l’utilisateur va visiter l’adresse /state sur notre application, notre état va devenir actif et
inversement, si on active notre état (via ui-sref ou $state.go()), l’URL sera mise à jour vers l’URL de l’état.

Si vous voulez passer des attributs à votre état, la syntaxe suivante est disponible. Pour en savoir plus, reportez-vous à la doc très complète sur le sujet : URL Parameters

var mystate = {
    url: '/state/:stateLabel',
    template: '{{ State.label }}',
    controller: function($stateParams) {
        this.label = $stateParams.stateLabel;
    },
    controllerAs: 'State'
};

Héritage

L’héritage permet d’imbriquer des états les uns dans les autres. Cette fonctionnalité vous permet de structurer efficacement la navigation de votre application et également de vos fichiers. En effet cette notion d’imbrication permet de modifier une partie de votre page à l’aide uniquement d’un directive ui-view dans votre template.

Un exemple de déclaration d’état imbriqué :

var country = {
    name: 'country',
    url: '/country',
    templateUrl: 'views/country/index.html',
};

var countryDetail ={
    name: 'country.detail',
    parent: country,
    url: '/detail',
    templateUrl: 'views/country/detail.html',
};

$stateProvider
    .state(country)
    .state(countryDetail);

Plusieurs choses concernant l’héritage :

  • Il faut définir name avec la « notation pointée » : country.detail
  • L’url de l’état countries.detail est automatiquement préfixée par l’url de son parent et devient : /country/detail.
  • On peut néanmoins définir une url particulière à l’aide des Absolute Routes
  • Au niveau des vues, lorsque countryDetail est activé, il s’injecte dans la directive ui-view du template de son parent
  • À noter qu’il existe la notion d’état abstrait qui peut être utile dans certains cas :
    • Pour préfixer l’url de ses enfants
    • Pour définir un template parent qui va encapsuler celui de ses enfants avec sa propre ui-view

Vues multiples

Les vues multiples offrent la possibilité d’avoir plusieurs vues dans un même template, c’est intéressant pour les composants réutilisables type widget de votre application.

Imaginons que dans mon application ma page pays doit afficher une barre de navigation spéciale, une table comportant des données sur les pays et une représentation graphique de ces données. Je peux utiliser les vues multiples afin de séparer les vues et logique de chacun des « composants » de ma page de cette manière :



    
var country = {
    views: {
        'countriesNavbar': {
            templateUrl: 'countriesNavbar.html',
            controller: function(){ ... }
        },
        'countriesTable': {
            templateUrl: 'countriestable.html',
            controller: function(){ ... }
        },
        'countriesGraph': {
            templateUrl: 'countriesgraph.html',
            controller: function(){ ... }
        },
    }
};

$stateProvider
    .state('country');

On peut définir la logique de tous de nos templates dans leur contrôleurs respectifs.

Pour aller plus loin

Les Les évenements de changement d’états ou encore les évenements de chargement de vue.

$urlRouterProvider vous servira à définir les redirections dans votre applications.

Et bien entendu la Documentation.

UI Router est un outil puissant qui vous permettra d’améliorer la navigation et la structure de votre application.
L’essayer c’est l’adopter, alors n’hésitez pas à nous faire vos retours d’expériences.