首页 文章

如何使用AngularJS Angular UI路由器制作自动动态面包屑

提问于
浏览
34

Web应用程序的一个关键组件是面包屑/导航 . 使用Angular UI路由器,将breadcrumb元数据与各个状态放在一起是有意义的,而不是在控制器中 . 手动为每个需要它的控制器创建面包屑对象是一项直接的任务,但它也是一个非常混乱的对象 .

我已经看到了一些使用Angular的自动面包屑的解决方案,但说实话,它们相当原始 . 某些状态,如对话框或侧面板不应该更新面包屑,但是当前的插件有角度,没有办法表达 .

另一个问题是面包屑的 Headers 不是静态的 . 例如,如果您转到“用户详细信息”页面,则面包屑 Headers 应该是用户的全名,而不是通用的“用户详细信息” .

需要解决的最后一个问题是使用父链接的所有正确的状态参数值 . 例如,如果您想要知道父状态需要 :companyId .

是否有角度的插件可以提供这种级别的面包屑支持?如果没有,最好的方法是什么?我不想让我的控制器混乱 - 我会有很多 - 我希望尽可能自动化和无痛 .

谢谢!

6 回答

  • 4

    深入挖掘ui-router的内部后,我明白了如何使用已解析的资源创建面包屑 .

    Here is a plunker to my directive.

    注意:我无法使此代码在plunker中正常工作,但该指令适用于我的项目 . 提供routes.js仅仅是为了如何设置面包屑的 Headers .

  • 40

    我制作了一个Angular模块,它根据ui-router的状态生成一个面包屑 . 你所说的所有功能都包括在内(我最近添加了在阅读这篇文章时忽略面包屑状态的可能性:-)):

    这是the github repo

    它允许针对控制器范围内插的动态标签(在嵌套/多个视图的情况下为“最深”) .

    国家链可以通过州选项进行定制(参见API reference

    该模块带有预定义的模板,并允许用户定义的模板 .

  • 0

    我曾经自己解决了这个问题,因为没有任何东西可用 . 我决定不使用 data 对象,因为我们不会影响面包屑 . 通过使用 breadcrumb 对象,我们可以避免自动继承 .

    对于实际的 title 属性,我使用 $interpolate . 我们可以将面包屑数据与解析范围结合起来,而无需在不同的地方进行解析 . 在我遇到的所有情况中,我只是想使用解析范围,所以这非常有效 .

    我的解决方案也处理i18n .

    $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.
    

    我不想将面包屑绑定到指令,因为我认为在我的应用程序中可能有多种方式可视化地显示面包屑 . 所以,我把它放到一个服务中:

    .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;
            }
        };
    })
    

    实际的breadcrumb指令变得非常简单:

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

    和模板:

    <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>
    

    从这里的屏幕截图中,您可以看到它在导航中完美运行:

    enter image description here

    以及html <title> 标签:

    enter image description here

    PS到Angular UI团队:请添加开箱即用的东西!

  • 0

    我想与大家分享我的解决方案 . 它的优点是不需要将任何东西注入控制器,并支持命名的痕迹痕迹标签,以及使用 resolve: 函数来命名面包屑 .

    示例状态配置:

    $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);
                }
            }
        })
    

    然后,您需要在指令的属性中指定面包屑标签(displayname)的位置:

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

    通过这种方式,指令将知道查看 $state.$current.data.displayName 的值以查找要使用的文本 .

    $ interpolate-able breadcrumb名称

    请注意,在最后一个状态( home.userList.detail )中,displayName使用通常的角度插值语法 {{ value }} . 这允许您引用状态配置中 resolve 对象中定义的任何值 . 通常,这将用于从服务器获取数据,如上面的用户名示例所示 . 请注意,由于这只是一个常规的Angular字符串,因此您可以在displayName字段中包含任何类型的有效Angular表达式 - 如上例中我们对其应用过滤器的示例 .

    演示

    这是关于Plunker的工作演示:http://plnkr.co/edit/bBgdxgB91Z6323HLWCzF?p=preview

    代码

    我认为将所有代码放在这里有点多,所以这里是GitHub:https://github.com/michaelbromley/angularUtils/tree/master/src/directives/uiBreadcrumbs

  • 0

    我不相信有内置功能,但所有工具都适合你,看看LocationProvider . 你可以简单地让导航元素使用这个以及你想知道的其他任何东西只需注入它 .

    Documentation

  • 23

    感谢@egervari提供的解决方案 . 对于那些需要将一些$ stateParams属性添加到面包屑的自定义数据中的人 . 我已经为key的值扩展了语法{:id}' Headers ' .

    .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}'
        }
    })
    

    这是一个Plunker示例 . 仅供参考 .

相关问题