diff --git a/src/state.js b/src/state.js
index 3330a8655..6a33feb58 100644
--- a/src/state.js
+++ b/src/state.js
@@ -1116,7 +1116,7 @@ function $StateProvider( $urlRouterProvider, $urlMatcherFactory) {
var nav = (state && options.lossy) ? state.navigable : state;
- if (!nav || !nav.url) {
+ if (!nav || nav.url === undefined || nav.url === null) {
return null;
}
return $urlRouter.href(nav.url, filterByKeys(objectKeys(state.params), params || {}), {
diff --git a/src/stateDirectives.js b/src/stateDirectives.js
index 3b5831885..ba730ce29 100644
--- a/src/stateDirectives.js
+++ b/src/stateDirectives.js
@@ -86,6 +86,7 @@ function $StateRefDirective($state, $timeout) {
link: function(scope, element, attrs, uiSrefActive) {
var ref = parseStateRef(attrs.uiSref, $state.current.name);
var params = null, url = null, base = stateContext(element) || $state.$current;
+ var newHref = null, isAnchor = element.prop("tagName") === "A";
var isForm = element[0].nodeName === "FORM";
var attr = isForm ? "action" : "href", nav = true;
@@ -102,7 +103,7 @@ function $StateRefDirective($state, $timeout) {
if (newVal) params = newVal;
if (!nav) return;
- var newHref = $state.href(ref.state, params, options);
+ newHref = $state.href(ref.state, params, options);
var activeDirective = uiSrefActive[1] || uiSrefActive[0];
if (activeDirective) {
@@ -134,8 +135,11 @@ function $StateRefDirective($state, $timeout) {
});
e.preventDefault();
+ // if the state has no URL, ignore one preventDefault from the directive.
+ var ignorePreventDefaultCount = isAnchor && !newHref ? 1: 0;
e.preventDefault = function() {
- $timeout.cancel(transition);
+ if (ignorePreventDefaultCount-- <= 0)
+ $timeout.cancel(transition);
};
}
});
diff --git a/test/stateDirectivesSpec.js b/test/stateDirectivesSpec.js
index 962a5471a..1e9e1bead 100644
--- a/test/stateDirectivesSpec.js
+++ b/test/stateDirectivesSpec.js
@@ -1,10 +1,11 @@
describe('uiStateRef', function() {
- var el, template, scope, document;
+ var timeoutFlush, el, el2, template, scope, document, _locationProvider;
beforeEach(module('ui.router'));
- beforeEach(module(function($stateProvider) {
+ beforeEach(module(function($stateProvider, $locationProvider) {
+ _locationProvider = $locationProvider;
$stateProvider.state('top', {
url: ''
}).state('contacts', {
@@ -74,29 +75,30 @@ describe('uiStateRef', function() {
}));
});
- describe('links', function() {
- var timeoutFlush, el2;
- beforeEach(inject(function($rootScope, $compile, $timeout) {
- el = angular.element('Details');
- el2 = angular.element('Top');
- scope = $rootScope;
- scope.contact = { id: 5 };
- scope.$apply();
+ function buildDOM($rootScope, $compile, $timeout) {
+ el = angular.element('Details');
+ el2 = angular.element('Top');
+ scope = $rootScope;
+ scope.contact = { id: 5 };
+ scope.$apply();
- $compile(el)(scope);
- $compile(el2)(scope);
- scope.$digest();
+ $compile(el)(scope);
+ $compile(el2)(scope);
+ scope.$digest();
- timeoutFlush = function() {
- try {
- $timeout.flush();
- } catch (e) {
- // Angular 1.0.8 throws 'No deferred tasks to be flushed' if there is nothing in queue.
- // Behave as Angular >=1.1.5 and do nothing in such case.
- }
+ timeoutFlush = function () {
+ try {
+ $timeout.flush();
+ } catch (e) {
+ // Angular 1.0.8 throws 'No deferred tasks to be flushed' if there is nothing in queue.
+ // Behave as Angular >=1.1.5 and do nothing in such case.
}
- }));
+ }
+ };
+
+ describe('links', function() {
+ beforeEach(inject(buildDOM));
it('should generate the correct href', function() {
expect(el.attr('href')).toBe('#/contacts/5');
@@ -217,7 +219,7 @@ describe('uiStateRef', function() {
expect($state.current.name).toEqual('top');
expect($stateParams).toEqualData({});
}));
-
+
it('should allow passing params to current state', inject(function($compile, $rootScope, $state) {
$state.current.name = 'contacts.item.detail';
@@ -243,6 +245,38 @@ describe('uiStateRef', function() {
}));
});
+ describe('links in html5 mode', function() {
+ beforeEach(function() {
+ _locationProvider.html5Mode(true);
+ });
+
+ beforeEach(inject(buildDOM));
+
+ it('should generate the correct href', function() {
+ expect(el.attr('href')).toBe('/contacts/5');
+ expect(el2.attr('href')).toBe('');
+ });
+
+ it('should update the href when parameters change', function() {
+ expect(el.attr('href')).toBe('/contacts/5');
+ scope.contact.id = 6;
+ scope.$apply();
+ expect(el.attr('href')).toBe('/contacts/6');
+ });
+
+ it('should transition states when the url is empty', inject(function($state, $stateParams, $q) {
+ // Odd, in html5Mode, the initial state isn't matching on empty url, but does match if top.url is "/".
+// expect($state.$current.name).toEqual('top');
+
+ triggerClick(el2);
+ timeoutFlush();
+ $q.flush();
+
+ expect($state.current.name).toEqual('top');
+ expect($stateParams).toEqualData({});
+ }));
+ });
+
describe('forms', function() {
var el, scope;