mercredi 23 octobre 2013

Conserver les valeurs des critères de recherche

Comment conserver lorsque l'on change de vue à l'intérieur d'une application AngularJS des données correspondant à l'état de la vue, comme les valeurs des critères d'une recherche ?

Imaginons qu'on affiche dans une vue une liste d'objets, avec des critères permettant de faire un filtrage en local. Et sur chaque objet, un lien permet de naviguer vers une vue des détails de l'objet. Quand l'utilisateur revient sur la liste, il s'attend à la retrouver dans le même état. Ça suppose que les valeurs de filtrage saisies soient conservées.

Or si ces valeurs sont simplement dans le scope de la vue, elles seront perdues à chaque changement de vue, car le scope de l'ancienne vue est détruit par AngularJS. La solution simple pour les conserver est de les stocker dans un service.


Vous pouvez voir un exemple sur GitHub, avec le lien vers la démo (c'est le même que pour l'article "Service AngularJS de notification"). Saisissez une valeur de filtre dans le catalogue, et changez de vue par exemple en cliquant sur l'image d'un des articles. Revenez ensuite au catalogue, par le lien du menu ou avec le bouton "Page précédente" du navigateur. La valeur du filtre a été conservée.

Il suffit pour cela de publier un service qui est un simple objet vide. Le code dans le fichier js/services.js se résume à cette ligne (appliquée à l'objet module) :

.value('search', {})

Tout service AngularJS est un singleton, on est assuré de récupérer à chaque fois le même objet, ce qui permet d'y conserver des données tant qu'on reste sur l'application.

Ce service est injecté au contrôleur CatalogCtrl de la vue catalogue, et publié dans le scope associé (dans le fichier js/controllers.js) :

$scope.search = search;

Dans le template partials/catalog.html, le binding du champ de saisie du filtre se fait par la directive ngModel sur une propriété du service, qu'on utilise aussi dans le filtre 'filter' :

<input ng-model="search.text"/>

et

<li ng-repeat="game in catalog | filter:search.text">

C'était simple, non ?


Et pour aller plus loin...


Bien sûr cette technique peut s'utiliser pour conserver toutes sortes de données liées à l'état d'une vue, comme des options de visualisation, ou le nombre d'éléments affichés simultanément dans le cas d'une liste paginée, ou encore les colonnes affichées d'une table si on permet à l'utilisateur de les choisir.

On peut créer un service un peu plus sophistiqué, avec une méthode qui renvoie l'état conservé d'une vue d'après un identifiant quelconque attribué à la vue et passé en paramètre, et qui l'initialise si nécessaire avec un état par défaut passé en second paramètre. Il n'y a plus qu'à utiliser cette méthode dans chaque contrôleur d'une vue qui a des données à conserver, et publier l'état dans le scope.

On aura une utilisation de ce service, appelons-le viewState, qui pourra ressembler à ça :

$scope.state = viewState.get('MainListView', {
    pageSize: 50,
    showComments : false
});

Et ce service viewState n'est pas compliqué à écrire, il suffit d'appeler sur le module :

.factory('viewState', function () {
    var states = {};
    return {
        get: function (id, defaultState) {
            return states[id] || (states[id] = defaultState);
        }
    };
})







1 commentaire: