Skip to content

Preventing Resolve On URL Change Without State Reload #2796

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
calebeno opened this issue Jun 6, 2016 · 16 comments
Closed

Preventing Resolve On URL Change Without State Reload #2796

calebeno opened this issue Jun 6, 2016 · 16 comments

Comments

@calebeno
Copy link

calebeno commented Jun 6, 2016

I've been working on a project and am having an issue with the resolve block. This is my use case: When transitioning to a state, a resolve block is completed. Once in the new state, a component determines whether or not the url needs to be changed. The goal is to remain in exactly the same state, but to modify the url. However, when I make the url modification using something like this:

$state.go($state.$current.self.name, {newparams}, {location: 'replace', reload: false, notify: false});

The entire state remains the same and the url is updated, which is the desired behavior. However, the resolve block is then called again, adding unnecessary server requests. Is it not possible to prevent the resolve block from running? I feel like using reload: false should prevent this.

Note: I commented something similar on issue #2146. Since it was closed and this is slightly different I have reposted as a different issue here.

@eddiemonge
Copy link
Contributor

Is it the resolve for that state and maybe not a child or parent state or something?

@christopherthielen
Copy link
Contributor

christopherthielen commented Jun 7, 2016

The reload option can only be used to force state(s) to reload. It can't be used the way you describe however. Reload: false won't prevent a params change from re-entering a state. In 0.x the only support for your use case is a query param with reloadOnSearch=false. In 1.0 you would use dynamic params.

@calebeno
Copy link
Author

calebeno commented Jun 7, 2016

@christopherthielen Would it be more accurate then that notify: false is preventing the state from reloading? And if that is the case, wouldn't it make sense that notify would also prevent the resolve block from initiating? As I understand it, notify: false is preventing the standard state change events from firing. If that is the case, then what initiates the resolve block? It couldn't be the $stateChangeStart event because that is theoretically not firing.

@eddiemonge The resolves that are rerun are those for the current state.

I recognize that a possible solution might be to pass a squashed param as a part of the new url and the resolve functions could then check that before running. However, we have 5 resolve functions in this current state, all of which would need the check as well as any other state which needed to do this. That seems like the wrong thing to do and a headache to maintain. The best situation I could envision is one in which an option param is passed to prevent resolve, whether that's via notify or something else.

@christopherthielen
Copy link
Contributor

christopherthielen commented Jun 7, 2016

notify: false prevents state change events from being fired, but does not prevent states from being exited/entered, which is what processes the resolves.

Your suggested workaround might work, but you'd have to cache the resolve results in a service and have a bunch of logic to manage that correctly.

The only supported mechanisms are reloadOnSearch in 0.x and dynamic in 1.0+


On a side note, I do not recommend anyone use notify: false. It only does what the documentation states (suppresses state change events), and this almost always causes other problems. It's been deprecated, and no longer exists in 1.0.

@calebeno
Copy link
Author

calebeno commented Jun 10, 2016

Alright, finally had some time to work on this. Here's what I ended up doing:

For the state:

.state('name', {
    url: '/path/:changer',
    params: {
        overwriteReload: {
            value: false,
            squash: true
        }
    },
    resolve: {
        doSomething: function($stateParams) {
            if ($stateParams.overwriteReload === true) {
                return;
            }
            // Do something
        },
        doSomethingElse: function($stateParams) {
            if ($stateParams.overwriteReload === true) {
                $stateParams.overwriteReload = false;
                return;
            }
            // Do something else
        }
    }
})

The last checker in the resolve must set the overwriteReload back to false to prevent weirdness with inheritance in new states.

For the actual overwrite (which I have placed inside a service in my application):

$state.go($state.$current.self.name,
            {changer: 'somethingNew', overwriteReload: true},
            {location: 'replace', notify: false});

Even though it may use notify: false which is not recommended, it does the job correctly in this instance. So far as I can tell anyway. The resolve logic does not run for those marked with the if block and the current controllers/state do not change or reload. The url is changed but no new entry is put into the browser history.

@calebeno calebeno changed the title Reload: false doesn't prevent resolve Preventing Resolve Jun 10, 2016
@calebeno calebeno changed the title Preventing Resolve Preventing Resolve On URL Change Without State Reload Jun 10, 2016
@calebeno
Copy link
Author

For completeness, I looked at reloadOnSearch but that did not seem to be an appropriate use for my case. In all other situations than the one where I need to overwrite the url, I want the state to behave as normal. From what I could tell, reloadOnSearch changes the behavior of that state for all cases.

@ilovett
Copy link

ilovett commented Jun 17, 2016

I am having this issue on consecutive calls. I call something like:

      $state.go('some-state', params, {
        location: true,
        notify: false,
        reload: false
      });

First time, the resolve functions in my state def are not called. However, the second time I call the $state.go above in a row, the resolve resolver functions in my state is called.

    $stateProvider
      .state('some-state', {
        url: '/some-state'
        resolve: {
          init: function(MyService) {
            // called on 2nd in a row $state.go
            return MyService.load();
          }
        },
      });

Call stack is coming from:

      // Resolve 'global' dependencies for the state, i.e. those not specific to a view.
      // We're also including $stateParams in this; that way the parameters are restricted
      // to the set that should be visible to the state, and are independent of when we update
      // the global $state and $stateParams values.
      dst.resolve = $resolve.resolve(state.resolve, locals, dst.resolve, state);

@ShakurOo
Copy link

Hello @calebeno,

I have actually the same issue : please see here

Can you developp your answer ? Cause actually, if I use $scope.go() into my returned promise, I enter in a loop (the $state reload and reload... event if the option parameter: reload set to false). And if I use $location, the $state is reload twice (so the result function is called twice too)

@calebeno
Copy link
Author

@ShakurOo, I don't believe that reload: false actually does anything. It seems to be an option that forces reload when true. See here for more info: http://angular-ui.github.io/ui-router/site/#/api/ui.router.state.$state. In regard to your linked code, I'm not sure I fully understand your issue. Though something I did see is that you commented that you were using this code:

(typeof $state.params.ville == 'undefined')

Which should probably be:

(typeof $stateParams.ville == 'undefined')

Other than that, I'm not sure exactly what to advise? My solution uses a squashed param to prevent reload. It's possible that could work for you?

@calebeno
Copy link
Author

I will also comment that, while my solution does work, it does not fix the underlying problem. However, based on the replies so far, I don't think we can expect this to be fixed in 0.x versions.

@ShakurOo
Copy link

ShakurOo commented Jun 24, 2016

Thank's for your quick reply :)

The issue is : When the $promise function is resolved, I have to change the URL (for build a correct url, because the user can type with space, special chars, ...).

So, for do this I tried to call $location service and redefine a new URL in the promise callback : $location.path('villes/' + data.slug).replace();

But with it, the resolve function is called again (I don't need it).

If I try to change the URL with : $state.go('villes.ville', {ville: data.slug})

This time the resolve function is called in loop ! (So my service which returns data is called endlessly ... (the client freeze).

And (typeof $stateParams.ville == 'undefined') is always true so the condition is never false. I don't know how I can call just once the resolve and change the part of url juste before load the controller :/

@calebeno
Copy link
Author

@ShakurOo If you want to use the solution I posted, you should wrap the functionality of your resolve function in an if block which checks for a param. I would recommend doing something like this in your state definition:

params: {
  preventResolve: {
    value: false,
    squash: true
  }
},

Then your resolve would have:

resolve: {
  init: function() {
    if (!preventResolve) {
      // Your stuff
      ...
      // if this is the last function in your resolve block set prevent back to false
      preventResolve = false;
    }
  }
}

Then, when you call $state.go make sure you pass the preventResolve: true. It won't show up in your url because of squash.

Also, in my case I have the controller calling a service which initiates the $state.go. Maybe this could be useful to you as well?

@ShakurOo
Copy link

@calebeno Yes I understood ! But if i waiting until the controller is loaded, the ui-view will load the template...

The interest for using the resolve function is it prevent the loading of the template view. The data is loaded before the view is displayed. There is another sujet here, I going to look if someone has any others tips. Thank's you :)

@raygig
Copy link

raygig commented Jun 25, 2016

I am experiencing the same behavior as @calebeno where I want to update the state but do not want to process the resolves again.

@christopherthielen a simple $state.go() option called resolve which you can set to false would skip the processing of resolves.

Thanks

@christopherthielen
Copy link
Contributor

We can't have an option "to skip processing of resolves" because this would cause fundamental inconsistencies in the state machine. When a state is being entered, its resolves must be processed.

However, by using dynamic: true on a parameter value, you can inform the state machine that a parameter change DOESNT mean the state is re-entered. See this plunker: http://plnkr.co/edit/Y0uMpUY45aphRgbpaJhg?p=preview

Here's how it works:

  • User clicks /articles/12345
  • Transition starts to the articles state with 12345 parameter value.
  • As the state is being entered:
  • - The resolves are fetched
  • - The onEnter hook is invoked
  • The onEnter hook checks if the parameter should is canonical or not
  • If it is not canonical, it waits for the transition to succeed, then starts a new one with the canonical parameter value.
  • Since the slug parameter is dynamic, the parameter changing doesn't cause the state to be re-entered.
  • The resolve is fetched only once.

Closing because I think the original question is answered by dynamic in 1.0.

Please open a new issue (and reference this one) if there are other use cases not solved by dynamic

@awdyson
Copy link

awdyson commented Apr 12, 2017

The resolve skipping technique above is working for me, but I can't seem to find a combination of transition options (notify: false, reload: '') that will prevent a controller reload. Any thoughts?

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

No branches or pull requests

7 participants