Skip to content

Commit e77d702

Browse files
committed
feat(uiSref): add ui-sref-on to expose transition promise
Fixes angular-ui#1863 added $q.when to the transition
1 parent 2fff59c commit e77d702

File tree

2 files changed

+112
-14
lines changed

2 files changed

+112
-14
lines changed

src/stateDirectives.js

+19-14
Original file line numberDiff line numberDiff line change
@@ -24,40 +24,40 @@ function stateContext(el) {
2424
* @restrict A
2525
*
2626
* @description
27-
* A directive that binds a link (`<a>` tag) to a state. If the state has an associated
28-
* URL, the directive will automatically generate & update the `href` attribute via
29-
* the {@link ui.router.state.$state#methods_href $state.href()} method. Clicking
30-
* the link will trigger a state transition with optional parameters.
27+
* A directive that binds a link (`<a>` tag) to a state. If the state has an associated
28+
* URL, the directive will automatically generate & update the `href` attribute via
29+
* the {@link ui.router.state.$state#methods_href $state.href()} method. Clicking
30+
* the link will trigger a state transition with optional parameters.
3131
*
32-
* Also middle-clicking, right-clicking, and ctrl-clicking on the link will be
32+
* Also middle-clicking, right-clicking, and ctrl-clicking on the link will be
3333
* handled natively by the browser.
3434
*
35-
* You can also use relative state paths within ui-sref, just like the relative
35+
* You can also use relative state paths within ui-sref, just like the relative
3636
* paths passed to `$state.go()`. You just need to be aware that the path is relative
37-
* to the state that the link lives in, in other words the state that loaded the
37+
* to the state that the link lives in, in other words the state that loaded the
3838
* template containing the link.
3939
*
4040
* You can specify options to pass to {@link ui.router.state.$state#go $state.go()}
4141
* using the `ui-sref-opts` attribute. Options are restricted to `location`, `inherit`,
4242
* and `reload`.
4343
*
4444
* @example
45-
* Here's an example of how you'd use ui-sref and how it would compile. If you have the
45+
* Here's an example of how you'd use ui-sref and how it would compile. If you have the
4646
* following template:
4747
* <pre>
4848
* <a ui-sref="home">Home</a> | <a ui-sref="about">About</a> | <a ui-sref="{page: 2}">Next page</a>
49-
*
49+
*
5050
* <ul>
5151
* <li ng-repeat="contact in contacts">
5252
* <a ui-sref="contacts.detail({ id: contact.id })">{{ contact.name }}</a>
5353
* </li>
5454
* </ul>
5555
* </pre>
56-
*
56+
*
5757
* Then the compiled html would be (assuming Html5Mode is off and current state is contacts):
5858
* <pre>
5959
* <a href="#/home" ui-sref="home">Home</a> | <a href="#/about" ui-sref="about">About</a> | <a href="#/contacts?page=2" ui-sref="{page: 2}">Next page</a>
60-
*
60+
*
6161
* <ul>
6262
* <li ng-repeat="contact in contacts">
6363
* <a href="#/contacts/1" ui-sref="contacts.detail({ id: contact.id })">Joe</a>
@@ -76,8 +76,8 @@ function stateContext(el) {
7676
* @param {string} ui-sref 'stateName' can be any valid absolute or relative state
7777
* @param {Object} ui-sref-opts options to pass to {@link ui.router.state.$state#go $state.go()}
7878
*/
79-
$StateRefDirective.$inject = ['$state', '$timeout'];
80-
function $StateRefDirective($state, $timeout) {
79+
$StateRefDirective.$inject = ['$state', '$timeout', '$q'];
80+
function $StateRefDirective($state, $timeout, $q) {
8181
var allowedOptions = ['location', 'inherit', 'reload', 'absolute'];
8282

8383
return {
@@ -134,7 +134,12 @@ function $StateRefDirective($state, $timeout) {
134134
if ( !(button > 1 || e.ctrlKey || e.metaKey || e.shiftKey || element.attr('target')) ) {
135135
// HACK: This is to allow ng-clicks to be processed before the transition is initiated:
136136
var transition = $timeout(function() {
137-
$state.go(ref.state, params, options);
137+
var $transition = $state.go(ref.state, params, options);
138+
if($transition && attrs.uiSrefOn) {
139+
scope.$eval(attrs.uiSrefOn, {
140+
$transition: $q.when($transition)
141+
});
142+
}
138143
});
139144
e.preventDefault();
140145

test/stateDirectivesSpec.js

+93
Original file line numberDiff line numberDiff line change
@@ -559,3 +559,96 @@ describe('uiView controllers or onEnter handlers', function() {
559559
expect(count).toBe(1);
560560
}));
561561
});
562+
563+
564+
ddescribe('uiSrefResolve', function() {
565+
var template = '<div><a ui-sref=".a" ui-sref-on="check($transition)" class="a">A</a></div>';
566+
567+
beforeEach(module('ui.router'));
568+
569+
var $timeout, defer;
570+
571+
beforeEach(module(function($stateProvider) {
572+
$stateProvider.state('top', {
573+
url: ''
574+
}).state('a', {
575+
url: '/a',
576+
template: 'Success',
577+
controller: function(a) {}, // require a
578+
resolve: {
579+
a: function($q) {
580+
defer = $q.defer();
581+
return defer.promise;
582+
}
583+
}
584+
});
585+
}));
586+
587+
beforeEach(inject(function(_$timeout_) {
588+
$timeout = _$timeout_;
589+
}));
590+
591+
function triggerClick(el, options) {
592+
options = angular.extend({
593+
metaKey: false,
594+
ctrlKey: false,
595+
shiftKey: false,
596+
altKey: false,
597+
button: 0
598+
}, options || {});
599+
600+
var e = document.createEvent("MouseEvents");
601+
e.initMouseEvent(
602+
"click", // typeArg of type DOMString, Specifies the event type.
603+
true, // canBubbleArg of type boolean, Specifies whether or not the event can bubble.
604+
true, // cancelableArg of type boolean, Specifies whether or not the event's default action can be prevented.
605+
undefined, // viewArg of type views::AbstractView, Specifies the Event's AbstractView.
606+
0, // detailArg of type long, Specifies the Event's mouse click count.
607+
0, // screenXArg of type long, Specifies the Event's screen x coordinate
608+
0, // screenYArg of type long, Specifies the Event's screen y coordinate
609+
0, // clientXArg of type long, Specifies the Event's client x coordinate
610+
0, // clientYArg of type long, Specifies the Event's client y coordinate
611+
options.ctrlKey, // ctrlKeyArg of type boolean, Specifies whether or not control key was depressed during the Event.
612+
options.altKey, // altKeyArg of type boolean, Specifies whether or not alt key was depressed during the Event.
613+
options.shiftKey, // shiftKeyArg of type boolean, Specifies whether or not shift key was depressed during the Event.
614+
options.metaKey, // metaKeyArg of type boolean, Specifies whether or not meta key was depressed during the Event.
615+
options.button, // buttonArg of type unsigned short, Specifies the Event's mouse button.
616+
null // relatedTargetArg of type EventTarget
617+
);
618+
el[0].dispatchEvent(e);
619+
}
620+
621+
it('should execute ui-sref-on code on click', inject(function($rootScope, $q, $compile, $state) {
622+
el = angular.element(template);
623+
template = $compile(el)($rootScope);
624+
$rootScope.$digest();
625+
626+
var a = angular.element(template[0].querySelector('.a'));
627+
628+
var state = 'start', transition = null;
629+
$rootScope.check = function(_transition) {
630+
state = 'resolving';
631+
transition = _transition;
632+
633+
transition.then(function() {
634+
state = 'resolved';
635+
});
636+
};
637+
638+
expect(state).toBe('start');
639+
expect(transition).toBeNull();
640+
expect(a.attr('class')).toBe('a');
641+
642+
triggerClick(a);
643+
$timeout.flush();
644+
645+
expect(state).toBe('resolving');
646+
expect(transition).not.toBeNull();
647+
648+
defer.resolve('done');
649+
$timeout.flush();
650+
651+
expect(state).toBe('resolved');
652+
}));
653+
654+
});

0 commit comments

Comments
 (0)