` tag) to a state. If the state has an associated
+ * URL, the directive will automatically generate & update the `href` attribute via
+ * the {@link ui.router.state.$state#methods_href $state.href()} method. Clicking
+ * the link will trigger a state transition with optional parameters.
*
- * Also middle-clicking, right-clicking, and ctrl-clicking on the link will be
+ * Also middle-clicking, right-clicking, and ctrl-clicking on the link will be
* handled natively by the browser.
*
- * You can also use relative state paths within ui-sref, just like the relative
+ * You can also use relative state paths within ui-sref, just like the relative
* paths passed to `$state.go()`. You just need to be aware that the path is relative
- * to the state that the link lives in, in other words the state that loaded the
+ * to the state that the link lives in, in other words the state that loaded the
* template containing the link.
*
* You can specify options to pass to {@link ui.router.state.$state#go $state.go()}
@@ -42,22 +42,22 @@ function stateContext(el) {
* and `reload`.
*
* @example
- * Here's an example of how you'd use ui-sref and how it would compile. If you have the
+ * Here's an example of how you'd use ui-sref and how it would compile. If you have the
* following template:
*
* Home | About | Next page
- *
+ *
*
*
- *
+ *
* Then the compiled html would be (assuming Html5Mode is off and current state is contacts):
*
* Home | About | Next page
- *
+ *
*
* -
* Joe
@@ -279,7 +279,63 @@ function $StateRefActiveDirective($state, $stateParams, $interpolate) {
};
}
+/**
+ * @ngdoc directive
+ * @name ui.router.state.directive:ui-state
+ *
+ * @requires ui.router.state.uiSref
+ *
+ * @restrict A
+ *
+ * @description
+ * Much like ui-sref, but will accept named $scope properties to evaluate for a state definition,
+ * params and override options.
+ *
+ * @param {string} ui-state 'stateName' can be any valid absolute or relative state
+ * @param {Object} ui-state-params params to pass to {@link ui.router.state.$state#href $state.href()}
+ * @param {Object} ui-state-opts options to pass to {@link ui.router.state.$state#go $state.go()}
+ */
+$StateRefDynamicDirective.$inject = [ 'uiSrefDirective' ];
+function $StateRefDynamicDirective (uiSrefDirective) {
+ return {
+ require: uiSrefDirective[0].require,
+ link: function (scope, element, attrs) {
+ var args = arguments;
+
+ function buildUiSref () {
+ var baseRef = scope.$eval(attrs.uiState);
+
+ if (attrs.uiStateParams) {
+ baseRef += '(' + JSON.stringify(scope.$eval(attrs.uiStateParams)) + ')';
+ }
+
+ return baseRef;
+ }
+
+ function runStateRefLink () {
+ angular.extend(args[2], {
+ uiSref: buildUiSref(),
+ uiSrefOpts: attrs.uiStateOpts,
+ });
+
+ uiSrefDirective[0].link.apply(null, args);
+ }
+
+ scope.$watch(function () {
+ return [
+ attrs.uiState,
+ attrs.uiStateParams,
+ attrs.uiStateOpts
+ ].map(angular.bind(scope, scope.$eval));
+ }, runStateRefLink, true);
+
+ runStateRefLink();
+ }
+ };
+}
+
angular.module('ui.router.state')
.directive('uiSref', $StateRefDirective)
.directive('uiSrefActive', $StateRefActiveDirective)
- .directive('uiSrefActiveEq', $StateRefActiveDirective);
+ .directive('uiSrefActiveEq', $StateRefActiveDirective)
+ .directive('uiState', $StateRefDynamicDirective);
diff --git a/test/stateDirectivesSpec.js b/test/stateDirectivesSpec.js
index c9b621b7d..9cb7303b9 100644
--- a/test/stateDirectivesSpec.js
+++ b/test/stateDirectivesSpec.js
@@ -291,6 +291,70 @@ describe('uiStateRef', function() {
}));
});
+ describe('links with dynamic state definitions', function () {
+ var template;
+
+ beforeEach(inject(function($rootScope, $compile, $state) {
+ el = angular.element('state');
+ scope = $rootScope;
+ scope.state = 'contacts';
+ template = $compile(el)(scope);
+ scope.$digest();
+ }));
+
+ it('sets the correct initial href', function () {
+ expect(angular.element(template[0]).attr('href')).toBe('#/contacts');
+ });
+
+ it('updates to the new href', function () {
+ expect(angular.element(template[0]).attr('href')).toBe('#/contacts');
+
+ scope.state = 'contacts.item({ id: 5 })';
+ scope.$digest();
+ expect(angular.element(template[0]).attr('href')).toBe('#/contacts/5');
+
+ scope.state = 'contacts.item({ id: 25 })';
+ scope.$digest();
+ expect(angular.element(template[0]).attr('href')).toBe('#/contacts/25');
+ });
+
+ it('retains the old href if the new points to a non-state', function () {
+ expect(angular.element(template[0]).attr('href')).toBe('#/contacts');
+ scope.state = 'nostate';
+ scope.$digest();
+ expect(angular.element(template[0]).attr('href')).toBe('#/contacts');
+ });
+
+ it('accepts param overrides', inject(function ($compile) {
+ el = angular.element('state');
+ scope.state = 'contacts.item';
+ scope.params = { id: 10 };
+ template = $compile(el)(scope);
+ scope.$digest();
+ expect(angular.element(template[0]).attr('href')).toBe('#/contacts/10');
+ }));
+
+ it('accepts option overrides', inject(function ($compile, $timeout, $state) {
+ var transitionOptions;
+
+ el = angular.element('state');
+ scope.state = 'contacts';
+ scope.opts = { reload: true };
+ template = $compile(el)(scope);
+ scope.$digest();
+
+ spyOn($state, 'go').andCallFake(function(state, params, options) {
+ transitionOptions = options;
+ });
+
+ triggerClick(template)
+ $timeout.flush();
+
+ expect(transitionOptions.reload).toEqual(true);
+ expect(transitionOptions.absolute).toBeUndefined();
+ }));
+ });
+
describe('forms', function() {
var el, scope;