web-dev-qa-db-fra.com

Comment faire des chapelures dynamiques automatisées avec un routeur AngularJS + Angular UI

Un élément clé des applications Web est le fil d'Ariane/la navigation. Avec Angular UI Router, il serait logique de placer les métadonnées du fil d'Ariane dans les différents états plutôt que dans vos contrôleurs. La création manuelle de l'objet de fil d'Ariane pour chaque contrôleur où cela est nécessaire est une tâche simple, mais également très compliquée.

J'ai déjà vu des solutions d'automatisation de Breadcrumbs avec Angular, mais pour être honnête, elles sont plutôt primitives. Certains états, comme les boîtes de dialogue ou les panneaux latéraux, ne doivent pas mettre à jour la chapelure, mais avec les addons actuels à angular, il n’ya aucun moyen de le dire.

Un autre problème est que les titres de chapelure ne sont pas statiques. Par exemple, si vous accédez à une page Détails de l'utilisateur, le titre de la barre de navigation devrait probablement être le nom complet de l'utilisateur, et non un "Détails de l'utilisateur" générique.

Le dernier problème à résoudre consiste à utiliser toutes les valeurs de paramètre d'état correctes pour les liens parents. Par exemple, si vous consultez une page de détail d'utilisateur d'une société, vous voudrez évidemment savoir que l'état parent nécessite un :companyId.

Existe-t-il des addons à angular qui fournissent ce niveau de support de chapelure? Si non, quelle est la meilleure façon de s'y prendre? Je ne veux pas encombrer mes contrôleurs - j'en aurai beaucoup - et je veux le rendre aussi automatisé que possible.

Merci!

34
egervari

J'ai moi-même résolu ce problème il y a quelque temps, car rien n'était disponible. J'ai décidé de ne pas utiliser l'objet data, car nous ne voulons pas réellement que nos titres de fil d'Ariane soient hérités par des enfants. Parfois, des dialogues modaux et des panneaux de droite insérés sont techniquement des "vues d'enfants", mais ils ne devraient pas affecter le fil d'Ariane. En utilisant plutôt un objet breadcrumb, nous pouvons éviter l'héritage automatique.

Pour la propriété actuelle title, j’utilise $interpolate. Nous pouvons combiner nos données de fil d'Ariane avec le scope de résolution sans avoir à le faire à un endroit différent. Dans tous les cas que j'ai eu, je voulais juste utiliser le scope de résolution de toute façon, donc ça marche très bien. 

Ma solution gère également i18n aussi.

$stateProvider
    .state('courses', {
        url: '/courses',
        template: Templates.viewsContainer(),
        controller: function(Translation) {
            Translation.load('courses');
        },
        breadcrumb: {
            title: 'COURSES.TITLE'
        }
    })
    .state('courses.list', {
        url: "/list",
        templateUrl: 'app/courses/courses.list.html',
        resolve: {
            coursesData: function(Model) {
                return Model.getAll('/courses');
            }
        },
        controller: 'CoursesController'
    })
    // this child is just a slide-out view to add/edit the selected course.
    // It should not add to the breadcrumb - it's technically the same screen.
    .state('courses.list.edit', {
        url: "/:courseId/edit",
        templateUrl: 'app/courses/courses.list.edit.html',
        resolve: {
            course: function(Model, $stateParams) {
                return Model.getOne("/courses", $stateParams.courseId);
            }
        },
        controller: 'CourseFormController'
    })
    // this is a brand new screen, so it should change the breadcrumb
    .state('courses.detail', {
        url: '/:courseId',
        templateUrl: 'app/courses/courses.detail.html',
        controller: 'CourseDetailController',
        resolve: {
            course: function(Model, $stateParams) {
                return Model.getOne('/courses', $stateParams.courseId);
            }
        },
        breadcrumb: {
            title: '{{course.name}}'
        }
    })
    // lots more screens.

Je ne voulais pas lier la chapelure à une directive, parce que je pensais qu'il pouvait y avoir plusieurs façons de montrer la chapelure de manière visuelle dans ma candidature. Alors, je l'ai mis dans un service:

.factory("Breadcrumbs", function($state, $translate, $interpolate) {
    var list = [], title;

    function getProperty(object, path) {
        function index(obj, i) {
            return obj[i];
        }

        return path.split('.').reduce(index, object);
    }

    function addBreadcrumb(title, state) {
        list.Push({
            title: title,
            state: state
        });
    }

    function generateBreadcrumbs(state) {
        if(angular.isDefined(state.parent)) {
            generateBreadcrumbs(state.parent);
        }

        if(angular.isDefined(state.breadcrumb)) {
            if(angular.isDefined(state.breadcrumb.title)) {
                addBreadcrumb($interpolate(state.breadcrumb.title)(state.locals.globals), state.name);
            }
        }
    }

    function appendTitle(translation, index) {
        var title = translation;

        if(index < list.length - 1) {
            title += ' > ';
        }

        return title;
    }

    function generateTitle() {
        title = '';

        angular.forEach(list, function(breadcrumb, index) {
            $translate(breadcrumb.title).then(
                function(translation) {
                    title += appendTitle(translation, index);
                }, function(translation) {
                    title += appendTitle(translation, index);
                }
            );
        });
    }

    return {
        generate: function() {
            list = [];

            generateBreadcrumbs($state.$current);
            generateTitle();
        },

        title: function() {
            return title;
        },

        list: function() {
            return list;
        }
    };
})

La directive actuelle du fil d'Ariane devient alors très simple:

.directive("breadcrumbs", function() {
    return {
        restrict: 'E',
        replace: true,
        priority: 100,
        templateUrl: 'common/directives/breadcrumbs/breadcrumbs.html'
    };
});

Et le modèle:

<h2 translate-cloak>
    <ul class="breadcrumbs">
        <li ng-repeat="breadcrumb in Breadcrumbs.list()">
            <a ng-if="breadcrumb.state && !$last" ui-sref="{{breadcrumb.state}}">{{breadcrumb.title | translate}}</a>
            <span class="active" ng-show="$last">{{breadcrumb.title | translate}}</span>
            <span ng-hide="$last" class="divider"></span>
        </li>
    </ul>
</h2>

Vous pouvez voir sur la capture d'écran que cela fonctionne parfaitement dans la navigation:

enter image description here

Ainsi que la balise html <title>:

enter image description here

PS à l'équipe d'interface utilisateur angulaire: S'il vous plaît ajouter quelque chose comme ça hors de la boîte!

40
egervari

J'aimerais partager ma solution à cela. Il présente l'avantage de ne nécessiter aucune injection dans vos contrôleurs et prend en charge les étiquettes de chapelure nommées, ainsi que l'utilisation de fonctions resolve: pour nommer votre chapelure. 

Exemple de configuration d'état:

$stateProvider
    .state('home', {
        url: '/',
        ...
        data: {
            displayName: 'Home'
        }
    })
    .state('home.usersList', {
        url: 'users/',
        ...
        data: {
            displayName: 'Users'
        }
    })
    .state('home.userList.detail', {
        url: ':id',
        ...
        data: {
            displayName: '{{ user.name | uppercase }}'
        }
        resolve: {
            user : function($stateParams, userService) {
                return userService.getUser($stateParams.id);
            }
        }
    })

Ensuite, vous devez spécifier l'emplacement de l'étiquette de fil d'Ariane (nom d'affichage) dans un attribut de la directive:

<ui-breadcrumbs displayname-property="data.displayName"></ui-breadcrumbs>

De cette façon, la directive saura regarder la valeur de $state.$current.data.displayName pour trouver le texte à utiliser. 

$ noms de chapelure interpolables

Notez que dans le dernier état (home.userList.detail), displayName utilise la syntaxe habituelle d'interpolation angulaire {{ value }}. Cela vous permet de référencer toutes les valeurs définies dans l'objet resolve dans la configuration d'état. Généralement, cela serait utilisé pour obtenir des données du serveur, comme dans l'exemple ci-dessus du nom d'utilisateur. Notez que, puisqu'il ne s'agit que d'une chaîne angulaire normale, vous pouvez inclure tout type d'expression angulaire valide dans le champ displayName - comme dans l'exemple ci-dessus où nous y appliquons un filtre.

Démo

Voici une démonstration de travail sur Plunker: http://plnkr.co/edit/bBgdxgB91Z6323HLWCzF?p=preview

Code

Je pensais que c'était un peu difficile de mettre tout le code ici, donc le voici sur GitHub: https://github.com/michaelbromley/angularUtils/tree/master/src/directives/uiBreadcrumbs

23
Michael Bromley

J'ai créé un module angulaire qui génère un fil d'Ariane basé sur les états de ui-router. Toutes les fonctionnalités dont vous parlez sont incluses (j'ai récemment ajouté la possibilité d'ignorer un état dans le fil d'Ariane lors de la lecture de cet article :-)):

Voici le github repo

Il permet des étiquettes dynamiques interpolées par rapport à la portée du contrôleur (la plus profonde en cas de vues imbriquées/multiples).

La chaîne d'états est personnalisable par les options d'état (Voir Référence API )

Le module est livré avec des modèles prédéfinis et permet des modèles définis par l'utilisateur.

4
ncuillery

Je ne crois pas que la fonctionnalité soit intégrée, mais tous les outils sont là pour vous. Consultez le LocationProvider . Vous pouvez simplement faire en sorte que les éléments de navigation utilisent ceci et tout ce que vous voulez savoir, vous ne pouvez que l'injecter.

Documentation

0
Gent

Après avoir approfondi les détails internes de ui-router, j'ai compris comment créer un fil d'Ariane à l'aide de ressources résolues.

Voici un plunker à ma directive.

NOTE: Je ne pouvais pas obtenir ce code pour fonctionner correctement dans le Punker, mais la directive fonctionne dans mon projet. Le fichier routes.js est fourni uniquement à titre d'exemple. Vous pouvez ainsi définir des titres pour votre fil d'Ariane.

0
Nailer

Merci pour la solution fournie par @egervari. Pour ceux qui ont besoin d’ajouter des propriétés $ stateParams dans les données personnalisées de chapelure. J'ai étendu la syntaxe {: id} pour la valeur de la clé 'title'.

.state('courses.detail', {
    url: '/:courseId',
    templateUrl: 'app/courses/courses.detail.html',
    controller: 'CourseDetailController',
    resolve: {
        course: function(Model, $stateParams) {
            return Model.getOne('/courses', $stateParams.courseId);
        }
    },
    breadcrumb: {
        title: 'course {:courseId}'
    }
})

Voici un Plunker exemple. FYI.

0
Indiana