【问题标题】:How to make Automated Dynamic Breadcrumbs with AngularJS + Angular UI Router如何使用 AngularJS + Angular UI Router 制作自动动态面包屑
【发布时间】:2014-03-25 02:11:18
【问题描述】:

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

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

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

需要解决的最后一个问题是为父链接使用所有正确的状态参数值。例如,如果您正在查看公司的用户详细信息页面,显然您会想知道父状态需要 :companyId

是否有任何 Angular 插件可以提供这种级别的面包屑支持?如果没有,最好的方法是什么?我不想弄乱我的控制器——我会有很多——我想让它尽可能自动化和轻松。

谢谢!

【问题讨论】:

  • 有完全相同的问题。试图找出一个好的方法来做到这一点。令人讨厌的是,状态的“数据”对象不像“解析”对象。在状态数据中访问依赖注入的东西会很有趣。

标签: javascript angularjs angular-ui breadcrumbs


【解决方案1】:

不久前我确实自己解决了这个问题,因为没有可用的东西。我决定不使用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;
        }
    };
})

实际的面包屑指令变得非常简单:

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

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

还有html&lt;title&gt;标签:

对 Angular UI 团队的 PS:请立即添加类似的内容!

【讨论】:

  • 抽象状态似乎打破了这一点,或者我做错了什么:)
  • 所以最终这确实奏效了。万一其他人遇到问题,我需要在指令的链接函数中添加一个调用 generate,并确保该指令存在于状态范围内的某个位置。在那之后一帆风顺......甚至处理抽象状态:)解决方案的任何方式+1,尤其是添加插值:)
  • 请注意,此解决方案需要与翻译服务相关的难以找到的依赖项。可以改进原始帖子以使其明确。
  • @sufyan.shoaib 我可能做了一些额外的更改,那是不久前的事了,我忘记了。但它看起来像here 有一个要点。
  • 请给我们一个关于这个美丽的演示或一个例子的链接?
【解决方案2】:

我想分享我对此的解决方案。它的优点是不需要将任何东西注入您的控制器,并支持命名面包屑标签,以及使用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);
            }
        }
    })

然后你需要在指令的一个属性中指定面包屑标签(显示名称)的位置:

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

这样,指令将知道查看$state.$current.data.displayName 的值以查找要使用的文本。

$interpolate-able 面包屑名称

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

演示

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

代码

我觉得把所有的代码都放在这里有点多,所以在GitHub上:https://github.com/michaelbromley/angularUtils/tree/master/src/directives/uiBreadcrumbs

【讨论】:

  • @michael m 收到此错误“错误:[$compile:tpload] 无法在面包屑.js 中加载模板:uiBreadcrumbs.tpl.html 我已将模板 url 更改为 -->bower_components/angular- utils-ui-breadcrumbs/uiBreadcrumbs.tpl.html.. 但仍然显示相同的错误...请给我一些建议...
  • @BijayRai 好吧,听起来模板路径有些问题。如果您无法修复它,请打开一个问题并包含您的代码:github.com/michaelbromley/angularUtils/issues
  • @michael 很抱歉出现了简单的拼写错误... :)
  • @michael 如果有一些指南或提示可以在面包屑中制作下拉菜单,那将会很有帮助.. :)
  • @BijayRai 是的,但这超出了问题的范围。这听起来不像是微不足道的改变。你为什么不分叉我的 repo 并自己尝试一下 - 这不是我在项目中的要求之一,所以我担心自己不会努力实现这样的东西。
【解决方案3】:

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

这里是the github repo

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

状态链可通过状态选项自定义(参见API reference

模块自带预定义模板,允许用户自定义模板。

【讨论】:

    【解决方案4】:

    我不相信有内置功能,但所有工具都为你准备好了,看看LocationProvider。您可以简单地让导航元素使用它,而您想知道的任何其他内容都只需注入它。

    Documentation

    【讨论】:

    • 这方面的文档基本上是不存在的。我也在使用 Angular UI Router,而不是 Angular JS 提供的 ngRoute。
    • 为您添加了文档链接。同样通过 Angular UI 路由器查看,在他们的示例中,位置似乎与路由器一起使用。
    • 感谢两位的反馈。考虑到 OP 答案的彻底性,我同意这是一个薄弱的答案。但是,我只是试图将 OP 引向不同的方向。他的解决方案看起来很可靠,我什至在下次尝试做类似的事情时将其添加为书签。但是,我不相信这种建议应该归为评论。我希望能澄清一下为什么它应该是评论。
    【解决方案5】:

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

    Here is a plunker to my directive.

    注意:我无法让此代码在 plunker 中正常工作,但该指令在我的项目中有效。 routes.js 仅用于举例说明如何为面包屑设置标题。

    【讨论】:

      【解决方案6】:

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

      .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 示例。仅供参考。

      【讨论】:

        猜你喜欢
        • 2018-01-28
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2023-04-01
        • 2019-01-29
        相关资源
        最近更新 更多