uiBreadcrumbs Directive
This is a directive that auto-generates breadcrumbs based on angular-ui-router routes.
Demo
You can see a working demo demonstrating most of the features here: http://plnkr.co/edit/bBgdxgB91Z6323HLWCzF?p=preview
Requirements
This directive is designed to work with angular-ui-router, and will not work with the default Angular router.
The design of the directive also relies on the use of nested states in order to auto-generate the breadcrumbs hierarchy. See more on nested states here: https://github.com/angular-ui/ui-router/wiki/Nested-States-%26-Nested-Views#inherited-custom-data Note that the use of nested states does not imply nested views. Often, in a usual breadcrumbs use case, you won't want to have to nest a new view each time you go down the breadcrumb trail. To avoid using nested views, you should use a named view and refer to it when configuring your states. See the demo and the example below for an idea of how this would work.
Installation
You can install with Bower:
bower install angular-utils-ui-breadcrumbs
Alternatively just download the files uiBreadcrumbs.js and uiBreadcrumbs.tpl.html. Using bower has the advantage of making version management easier.
Usage
Assuming you already have your app configured to use ui-router, you then need to put this directive somewhere and use it like so:
<ui-breadcrumbs displayname-property="data.displayName"
[template-url=""]
[abstract-proxy-property=""]>
</ui-breadcrumbs>
displayname-property(required) This attribute must point to some property on your state config object that contains the string you wish to display as the route's breadcrumb. If none is specified, or if the specified property is not found, the directive will default to displaying the route's name.template-url(optional) Use this attribute to specify the URL of theuiBreadcrumbs.tpl.htmlfile. Alternatively this may be configured in the JavaScript file itself, in which case this attribute would not be needed.abstract-proxy-property(optional) Used when working with abstract states. See the section on working with abstract states below for a full explanation.
Example setup
He is an example that illustrates the main features of the directive:
angular.module('yourModule').config(function($stateProvider) {
$stateProvider
.state('home', {
url: '/',
views: {
'content@': {
templateUrl: ...
}
},
data: {
displayName: 'Home'
}
})
.state('home.usersList', {
url: 'users/',
views: {
'content@': {
templateUrl: ...
}
},
data: {
displayName: 'Users'
}
})
.state('home.userList.detail', {
url: ':id',
views: {
'content@': {
templateUrl: ...
}
},
data: {
displayName: '{{ user.firstName }} {{ user.lastName | uppercase }}'
}
resolve: {
user : function($stateParams, userService) {
return userService.getUser($stateParams.id);
}
}
})
.state('home.userList.detail.image', {
views: {
'content@': {
templateUrl: ...
}
},
data: {
displayName: false
}
})
// in the app template somewhere
<ui-breadcrumbs displayname-property="data.displayName"></ui-breadcrumbs>
<div ui-view="content"></div>
The first two states are straightforward. The property specified in the displayname-property attribute can be seen
to exist in the config object, the value of which is a string with the name we want to display in the breadcrumb.
The third state illustrates how we can use a resolved value as our breadcrumb display name. This is done by using the
regular Angular interpolation syntax {{ value }}. In this case, user corresponds to the resolve function that is using our
imaginary userService to asynchronously grab an object containing the details of the current user. You can also see that, just like in any Angular interpolation
string, you can reference properties of objects, use filters and so on.
The fourth state illustrates that if we don't want a state to show up in the breadcrumbs, we should set the
display name to false.
Working With Abstract States
AngularUI Router provides the option of setting up abstract states, which by definition cannot be transitioned to. Therefore, we cannot show them in the breadcrumbs, as clicking on an abstract state would cause the router to try to transition to that state, which results in an exception.
Therefore, the default behaviour is to ignore abstract states and skip that level of the breadcrumb hierarchy.
However, in many cases this behaviour would not be desirable. Consider the following setup (taken from the ui-router docs):
$stateProvider
.state('contacts', {
abstract: true,
url: '/contacts',
// Note: abstract still needs a ui-view for its children to populate.
// You can simply add it inline here.
template: '<ui-view/>'
})
.state('contacts.list', {
// url will become '/contacts/list'
url: '/list'
//...more
})
.state('contacts.detail', {
// url will become '/contacts/detail'
url: '/detail',
//...more
})
In this case, if we were in the contacts.detail state, the breadcrumbs would only display that state and ignore the parent state as it
is abstract. What we really want to do is substitute the contacts.list state for the parent state. This is because, logically, the
list of contacts is one level up from the detail page, even though they are strictly at the same level in the $state definition.
In order to achieve this substitution, we can use the abstract-proxy-property attribute on our directive. This tells the directive
to look for the specified property on the state config object, where it should find the name of the state to use instead of the abstract state.
To implement this, we would modify the above example to look like this:
$stateProvider
.state('contacts', {
abstract: true,
url: '/contacts',
template: '<ui-view/>'
data: {
breadcrumbProxy: 'contacts.list'
}
})
.state('contacts.list', {
url: '/list'
//...more
})
.state('contacts.detail', {
url: '/detail',
//...more
})
The directive element would then look like this:
<ui-breadcrumbs displayname-property="data.displayName" abstract-proxy-property="data.breadcrumbProxy"></ui-breadcrumbs>
Now, when we are in the contacts.detail state, the breadcrumbs will show the contacts.list as the immediate parent,
rather than the abstract contacts state.
Styling
The template structure is based on the Bootstrap 3 breadcrumbs component, so it
includes an active class to signify the current (left-most) item in the breadcrumbs list. You can, of course, modify the template as needed
or simply define your own CSS to fit it with your app's style.
Note on the data object
A potential "gotcha" is the fact that the data object gets inherited by child states (see docs here).
If you want to avoid this behaviour, don't use the data object to define your breadcrumb's displayname-property. Use another property on the .state() config object instead.
Credits
I used some ideas and approaches from the following sources: