Skip to content
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

Preventing state reload on URL change #427

Closed
roryf opened this issue Sep 16, 2013 · 32 comments
Closed

Preventing state reload on URL change #427

roryf opened this issue Sep 16, 2013 · 32 comments

Comments

@roryf
Copy link
Contributor

roryf commented Sep 16, 2013

I currently use the following code to prevent my route, and therefore controller, being reloaded when I update the $location:

angular.module('Shared').service('location', function($location, $route, $rootScope) {
  $location.skipReload = function () {
    var lastRoute = $route.current;
    var un = $rootScope.$on('$locationChangeSuccess', function () {
        $route.current = lastRoute;
        un();
    });
    return $location;
  };
  return $location;
});

along with:

location.skipReload().path("/newpath").replace();

Can I achieve the same behaviour with ui-router?

I tried simply replacing $route above with $state but this still resulted in a state change. My use case is I only have 'new' and 'edit' states which are exactly the same, when a user saves within the 'new' state I want to update the URL to include the saved id but not reload the view.

@roryf
Copy link
Contributor Author

roryf commented Sep 16, 2013

For anyone interested, the solution was:

angular.module('Shared').service('location', function($location, $rootScope) {
  $location.skipReload = function () {
    var un = $rootScope.$on('$stateChangeStart', function (event) {
        event.preventDefault();
        un();
    });
    return $location;
  };
  return $location;
});

@roryf roryf closed this as completed Sep 16, 2013
@bdecarne
Copy link

Hi,

I need this exact feature.

Your first solution work as expected : the URL is updated without reload the view.

Your second one with ui-router doesn't work as expected : it doesn't reload the view but the URL update is canceled. So neither reload ans update occurs.

Did you miss something in your solution ?

@see #64

@roryf
Copy link
Contributor Author

roryf commented Jan 25, 2014

Yeah, the code above took advantage of a bug in ui-router that since got fixed, I didn't realise it at the time. We're now using this, which also uses the decorator pattern rather than adding a new service:

function locationDecorator($location, $rootScope) {
  var skipping = false;

  $rootScope.$on('$locationChangeSuccess', function(event) {
    if (skipping) {
      event.preventDefault();
      skipping = false;
    }
  });

  $location.skipReload = function() {
    skipping = true;
    return this;
  };

  return $location;
}

angular.module('Shared').config(['$provide', function($provide) {
    $provide.decorator('$location', locationDecorator);
  }]);

and can be used as

$location.skipReload().path('/new/path').replace();

@msafi
Copy link

msafi commented Feb 4, 2014

Hmm, what's wrong with using reloadOnSearch: false which is supported by both ngRoute (see docs) and ui.router (see docs)?

@roryf
Copy link
Contributor Author

roryf commented Feb 4, 2014

If my understanding of reloadOnSearch is correct, it only changes behaviour when the query string or hash change, not the URL path, which is what was required here.

@andremendonca
Copy link

@roryf your new version to skip reload is not working - view still reloads after calling $location.path. At least for me. This is the event call order I have:
1 - $locationChangeStart
2 - $stateChangeStart
3 - $locationChangeSuccess
4 - $stateChangeSuccess

As far as I understand, when we preventDefault on "$locationChangeSuccess" ui-router already did the work.

@nateabele
Copy link
Contributor

Probably an event-firing order issue. We'll have to figure out an alternative solution.

@teisnet
Copy link

teisnet commented Feb 13, 2014

I got the same use case as described above (going from route "/new" to "/{id}") I hope it will be possible to have a consistent way to do this without reloading the controller. = +1

@yohairosen
Copy link

has anyone found a new solution to the problem?

@cheahkhing
Copy link

this is really bad, so far i can't find any solution yet to this problem. :(

@andremendonca
Copy link

instead of using the $location I used the $urlMatcher to get the state from a url and change the state instead of the url.

the good part of this approach is that the ui-router doesn't get lost after the url change

I created a provider to give you the idea, it setup the routing with a collection, and search the route on it, maybe it helps:
https://gist.github.com/andremendonca/06a1fbc486a4a001d871

@nateabele
Copy link
Contributor

I'll have a permanent fix for this shortly. Just finishing testing.

@nateabele nateabele reopened this Apr 15, 2014
@nateabele
Copy link
Contributor

Fixed in c72d8ce. See example code here:

ui-router/src/urlRouter.js

Lines 212 to 242 in c72d8ce

* @example
* <pre>
* var app = angular.module('app', ['ui.router.router']);
*
* app.config(function ($urlRouterProvider) {
*
* // Prevent $urlRouter from automatically intercepting URL changes;
* // this allows you to configure custom behavior in between
* // location changes and route synchronization:
* $urlRouterProvider.deferIntercept();
*
* }).run(function ($rootScope, $urlRouter, UserService) {
*
* $rootScope.$on('$locationChangeSuccess', function(e) {
* // UserService is an example service for managing user state
* if (UserService.isLoggedIn()) return;
*
* // Prevent $urlRouter's default handler from firing
* e.preventDefault();
*
* UserService.handleLogin().then(function() {
* // Once the user has logged in, sync the current URL
* // to the router:
* $urlRouter.sync();
* });
* });
*
* // Configures $urlRouter's listener *after* your custom listener
* $urlRouter.listen();
* });
* </pre>

@ktalebian
Copy link

Sorry, I still cannot figure out how to do this. How did the fix help this out? What is the current way of changing the URL without a reload?

@intellix
Copy link

Forked ui-router, created a release to play with and tried out the deferIntercept(); stuff. Used the example given in the ngdoc but not quite sure if it's working correctly.

The initial location change is deferred correctly and doesn't update until the sync(); is called. After that the other URLs seem to load instantly despite going through the same listener that I specified.

Either way, I don't believe the deferredInterception is for doing what the original poster is asking. DeferredInterception seems to be about stopping a state from loading at all (along with the URL).

The question above I believe is to stop the reloading of the controller/reloading of the view (ng-repeat re-running when something like a routeParameter is changed. If you have an ng-repeat with say 1,000 items and change a stateParam. Your may just want to just hide/show a tab, but all 1,000 items are going to get ng-repeated every time.

@charlie-s
Copy link
Contributor

I'm getting this to work in a modal service via:

var listener = $rootScope.$on('$locationChangeStart', function(event) {
    event.preventDefault();
    listener();
});
$window.history.pushState({}, '', options.href);

@jordangarside
Copy link

@intellix Did you ever find anything out about this issue? I for the life of me can't figure out a way around this.

@dmarcelino
Copy link

@roryf, your decorator worked perfectly for me. So far it was the cleanest solution I could find, thank you!

@vmihailenco
Copy link

@roryf thanks for the decorator. This is fixed version that uses $delegate:

locationDecorator = ($delegate, $rootScope) ->
  skipping = false

  $rootScope.$on '$locationChangeSuccess', (event) ->
    if skipping
      event.preventDefault()
      skipping = false

  $delegate.skipReload = ->
    skipping = true
    return this

  return $delegate


mod.config ($provide) ->
  $provide.decorator('$location', locationDecorator)

@mbernath
Copy link

@roryf, thanks for your decorator solution! I can confirm it works with ui-router 0.2.13 and angular 1.2.14.

@heygrady
Copy link

This seems to be a poorly documented option of the $state.go() function. Setting the notify options to false seems to keep everything from refreshing.

//change state without refreshing everything
$state.go('my-state', { id: myId }, { notify: false });

@jameskleeh
Copy link

@heygrady That worked for me

@soumak77
Copy link

soumak77 commented Apr 6, 2015

@heygrady Thanks for the tip, worked great!

@elfan
Copy link

elfan commented May 27, 2015

For some reasons, it didn't work for me.
But after tries and errors, accidentally I found a simple solution that works for me.

$state.params.id = id;  //updating the $state.params prevents reloading the state
$location.path('/folders/' + id);

I hope it helps someone.

Note: I'm using angularjs 1.2.7 and ui-router 0.0.1 (I know it's old).
I do also have a custom global $stateChangeStart handler somewhere else but most likely not related to this solution (it is for doing some validation whether the target state is allowed or not).
Another note: back and forward button still reloads the state

@intellix
Copy link

This will allow what you're after. You can disable the notify on a link so the URL changes but doesn't reload your controller: #1875

@jeremieca
Copy link

Hello, @nateabele

It seems your code break the replace function

  $location.path('http://...').replace();

Am I wrong ?

@xaun
Copy link

xaun commented Jul 1, 2015

@roryf legend mate! Worked on angular 1.3.16 & angular-ui-router 0.2.15.

@nateabele
Copy link
Contributor

@jeremieca Show me.

@jeremieca
Copy link

@nateabele Thanks, it solved. I made a mistake :) Sorry.

@garmoshka-mo
Copy link

@roryf
This decorator raises Circular Dependency (maybe only in latest angular)
#427 (comment)

Inside of decoration function must be used $delegate argument instead of $location:

angular.module('Shared', []).config(['$provide', function($provide) {
    $provide.decorator('$location', locationDecorator);
    function locationDecorator($delegate, $rootScope) {
        var skipping = false;

        $rootScope.$on('$locationChangeSuccess', function(event) {
            if (skipping) {
                event.preventDefault();
                skipping = false;
            }
        });

        $delegate.skipReload = function() {
            skipping = true;
            return this;
        };

        return $delegate;
    }
}]);

@garmoshka-mo
Copy link

Though event.preventDefault(); didn't worked for me. The very first solution was better.

Created lib from that: https://github.com/anglibs/angular-location-update

@rockallite
Copy link

@roryf @garmoshka-mo Great work! However, $location.skipReload().search(yourParams) won't update $stateParams. Must be manually updated by angular.extend($stateParams, yourParams) afterwards.

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