Skip to content

Notify: false prevents state transition entirely (not just events) #1158

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Closed
homerjam opened this issue Jun 23, 2014 · 19 comments
Closed

Notify: false prevents state transition entirely (not just events) #1158

homerjam opened this issue Jun 23, 2014 · 19 comments

Comments

@homerjam
Copy link

Hi,

I've created a Plunker here

As shown in the example I think one would expect to be able to use event.preventDefault() in the $stateChangeStart event to prevent a transition, then later trigger that same transition manually with {notify: false} to prevent the event being triggered and therefore the state transition being blocked again (infinite loop).

However the viewDirective is relying on $stateChangeSuccess internally which is also prevented by {notify: false}.

One work around is like so:

$state.go(toState.name, toParams, {notify: false}).then(function() {
    $rootScope.$broadcast('$stateChangeSuccess', toState, toParams, fromState, fromParams);
});

I think perhaps the best solution here would be to use an alternative event internally to trigger the updateView() in viewDirective, or alternatively extend notify to target start/success events separately.

Thanks

@gfreeau
Copy link

gfreeau commented Jul 16, 2014

+1 There are many open issues like this regarding $stateChangeStart and preventDefault. It's a common use case to run some kind of logic before state changes such as for authentication and authorization.

There isn't currently a good way to do this as the state change happens before resolves as well.

As an example, in my application, when the user logs in, their json web token is saved to local storage. When they visit the application again, that token must be used to check if it's still valid and then get the current user permissions from the server. This needs to happen before states should be initializing.

I was able to achieve this in the $stateChangeStart event, using event.preventDefault and a promise from my auth service and your workaround of broadcasting the $stateChangeSuccess event manually. If you don't do this, an infinite loop happens.

This same issue is being discussed in other issues such as #898.

People are also trying to achieve the same outcome in other ways, such as through a global angular resolve that happens before states try to initialize, such as in #946.

See also #1139, #618, #178 and #401.

@ProLoser
Copy link
Member

ProLoser commented Dec 7, 2014

Why is an app-level resolve not acceptable? The only thing I can imagine being an inconvenience is sending the user back somewhere if the resolve fails vs avoiding the entire resolution process entirely, but then this creates 2 separate places where we're doing querying for data we may want to use (such as if the current user is logged in). My vote is just to use a top-level resolve and close this issue.

@gfreeau
Copy link

gfreeau commented Dec 8, 2014

@ProLoser, this can indeed be solved with app level resolves. In my application I ended up using the angular deferred bootstrap and removed my $startChangeStart listener.

The only downside is that you cannot use promises on every request, you can only do your authentication promises on the first load. So if you wanted to check Authentication on every request or requests after the initial load, you have to rely on the data you retrieved on app load, instead of the data at the time of the transition.

@christopherthielen
Copy link
Contributor

Promises per transition is coming soon

@charandas
Copy link
Contributor

👍

@leonpapazianis
Copy link

Hi guys,

Anybody knows if this bug is actually resolved? Is the $stateChangeCancel event helping on that? I am trying to find any relevant documentation on the new releases (0.2.15) but I cant really pinpoint an actual solution. @christopherthielen Chris is the promise based transitions still in the roadmap?

Regards..

@tihomir-kit
Copy link

Hi,

I would also like to know what would be a workaround for this.

Cheers

@christopherthielen
Copy link
Contributor

@pootzko that depends on what you're trying to do (assuming with notify: false). In @homerjam's example, I think managing the infinite loop using a separate variable like retryInProgress would work.

$on("$stateChangeStart", event, to, toParams) {
  if (to.retryInProgress) {
    to.retryInProgress = false;
    return;
  }
  // do  stuff
  event.preventDefault();
  to.retryInProgress = true;
  $state.go(to, toParams);
}

@leonpapazianis yes, it is on the roadmap. You can use the proof-of-concept I added to ui-router-extras for now. See #1257 for details. I think code like this should work:

$on("$stateChangeStart", event, to, toParams) {
  // UI-Router Extras decorates the injector so you can do this in a event handler... 
  // UI-Router 1.0 will only allow $transition$ injection in places where resolves are normally injected.
  var t = $injector.get("$transition$"); 
  t.promise.then(transitionSuccessFn);
}

@tihomir-kit
Copy link

Thank you for the very prompt reply :)

Your first example helped me solve my issue.

@arjunasuresh3
Copy link

+1

@eddiemonge
Copy link
Contributor

@blowsie
Copy link

blowsie commented Sep 16, 2015

I can confirm this still happens for me on the feature branch

@adamzerner
Copy link

I'm having the issue as well: Plunker.

I think it'd be helpful to note this bug in the docs for notify.

@MadUser
Copy link

MadUser commented Mar 15, 2016

I solved the notify problem with separate inner success event:
angular-ui-router.js

if (options.notify) {
        /**
         * @ngdoc event
         * @name ui.router.state.$state#$stateChangeSuccess
         * @eventOf ui.router.state.$state
         * @eventType broadcast on root scope
         * @description
         * Fired once the state transition is **complete**.
         *
         * @param {Object} event Event object.
         * @param {State} toState The state being transitioned to.
         * @param {Object} toParams The params supplied to the `toState`.
         * @param {State} fromState The current state, pre-transition.
         * @param {Object} fromParams The params supplied to the `fromState`.
         */

          $rootScope.$broadcast('$stateChangeSuccess', to.self, toParams, from.self, fromParams);
        }
        $rootScope.$broadcast('$stateChangeSuccessInner', to.self, toParams, from.self, fromParams);

I call the stateChangeSuccessInner event anyway(of course I had to change the event listeners name as well)

@blowsie
Copy link

blowsie commented Apr 19, 2016

@eddiemonge Can we please re-open this issue?

@rahpuser
Copy link

rahpuser commented Jun 7, 2016

I'm having the same issue on 0.3.1. The issue should be pointed out in the docs. Is there a roadmap for solving this issue ?

Update: Seems like the issue is fixed in the alpha version. at least in '1.0.0-alpha.5'

@christopherthielen
Copy link
Contributor

Update: Seems like the issue is fixed in the alpha version. at least in '1.0.0-alpha.5'

Yes, the $transitions service has transition hooks such as onStart, which can be used to "pause" a transition.

@rahpuser
Copy link

Hi, any updates on this?

@christopherthielen
Copy link
Contributor

For anyone searching, there will not be updates on this issue because 1.0 deprecates the state events. {notify: false} no longer has any effect.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

No branches or pull requests