AngularJS: Change hash and route without completely reloading controller

37,729

Solution 1

Had the very same challange,

Found a hack in another StackOverflow response that did the trick

Fairly clean solution - all I did was to add these lines to the controller that sets $location.path:

var lastRoute = $route.current;
$scope.$on('$locationChangeSuccess', function(event) {
    $route.current = lastRoute;
});

..and made sure $route in injected into the controller of course.

But still, feels like "DoNotFollowRoutesOnPathChange" is a missing feature in AngularJS.

/Jens

Update: Since listening to this event effectively kills further usage of $routeProvider configs, I had to limit this catch to current path only:

    var lastRoute = $route.current;
    if ($route.current.$route.templateUrl.indexOf('mycurrentpath') > 0) {
        $route.current = lastRoute;         
    }

Getting ugly...

Solution 2

Brief Answer:

You can use the $location.search() method as you mentioned. You should listen to the "$routeUpdate" event on scope instead of other route events. $route API.

Explanation:

  1. First of all (you already know), add reloadOnSearch: false to your $routeProvider:

    $routeProvider.when('/somewhere', {
        controller: 'SomeCtrl',
        reloadOnSearch: false
    })
    
  2. Change your anchor tag href or ng-href to href="#/somewhere?param=value" this will trigger $routeChangeSuccess event if the path part (/somewhere) is not the same as current location. Otherwise it will trigger $routeUpdate event.

  3. Listen event on scope:

    $scope.$on("$routeUpdate", function(event, route) {
        // some code here
    });
    
  4. If you want to change search params in code, you can use $location.search() method. $location.search API.

Solution 3

If you set reloadOnSearch to false, you can set the ?a=b&c=d portion of the url without reload. You can't change the actual location prettily, though, without a reload.

Solution 4

Edit

Better approach when using ngRoute:

/**
 * Add skipReload() to $location service.
 *
 * See https://github.com/angular/angular.js/issues/1699
 */
app.factory('location',
  ['$rootScope', '$route', '$location',
  function($rootScope, $route, $location) {

  $location.skipReload = function() {
    var lastRoute = $route.current;

    var deregister = $rootScope.$on('$locationChangeSuccess',
                                    function(e, absUrl, oldUrl) {
      console.log('location.skipReload', 'absUrl:', absUrl, 'oldUrl:', oldUrl);
      $route.current = lastRoute;
      deregister();
    });

    return $location;
  };

  return $location;
}]);

How to use:

app.controller('MyCtrl', ['$scope', 'location', function($scope, location) {
  $scope.submit = function() {
    location.skipReload().path(path);
  };
}]);

Old answer

I have written a reusable factory for that based on Jens X Augustsson answer:

app.factory('DoNotReloadCurrentTemplate', ['$route', function($route) {
  return function(scope) {
    var lastRoute = $route.current;
    scope.$on('$locationChangeSuccess', function() {
      if (lastRoute.$$route.templateUrl === $route.current.$$route.templateUrl) {
        console.log('DoNotReloadCurrentTemplate',
                    $route.current.$$route.templateUrl);
        $route.current = lastRoute;
      }
    });
  };
}]);

Works with AngularJS 1.0.6

How to use:

app.controller('MyCtrl',
  ['$scope', 'DoNotReloadCurrentTemplate',
  function($scope, DoNotReloadCurrentTemplate) {

  DoNotReloadCurrentTemplate($scope);
}]);

AngularJS issue here: https://github.com/angular/angular.js/issues/1699

Solution 5

If you land here in 2015: The real answer here is to use none of these hacks (I dare name them so, because by using any of the methods listed above you will lose the possibility to use resolve and the likes) but to switch to ui-router.

Here's a handy presentation on the differences. Implementation should be as simple as swapping $route for $state and converting the states to names.

I'm currently switching over to a method where i will refer with an a href to a route, with an optional get parameter that changes the state without reloading it. For more on this, look at the 'params' section here

Share:
37,729
doubledriscoll
Author by

doubledriscoll

Updated on December 14, 2020

Comments

  • doubledriscoll
    doubledriscoll over 3 years

    I have a controller, with a route like this:

    #/articles/1234

    I want to change the route without completely reloading the controller, so I can keep the position of other stuff in the controller constant (lists that scroll)

    I can think of a few ways to do this, but they're all pretty ugly. Is there a best practice for doing something like this? I tried experimenting with reloadOnSearch: false, but it doesn't seem to change anything.

  • doubledriscoll
    doubledriscoll over 11 years
    I can change the query parameters without reloading the whole page? As in, before the hash? Or do you mean if I have the ?a=b&c=d at the end, after the hash?
  • Andrew Joslin
    Andrew Joslin over 11 years
    Yeah, you can change the query parameters with $location.search('a','b');
  • doubledriscoll
    doubledriscoll over 11 years
    Hmm... I don't know if that's ideal for this app, I'd rather stay consistent with the hash being the definitive route. I was considering having a controller that would be the route's path, which would send events to show and hide the actual controller, and only fully update it if it's being loaded for the first time. That sounds pretty ugly though, so I'm not sure if it's the best solution.
  • MalucoMarinero
    MalucoMarinero about 11 years
    I used this setup for something I'm working on, I think you're better off listening for $route.current.$$route.controller, ie $route.current.$$route.controller == 'controllerName' That's a little more reliable than playing path games.
  • SteveShaffer
    SteveShaffer almost 11 years
    After trying to get this to work in my project, I settled on listening for $route.current.params.reload == 'false' where I cause the $location change to happen with something like $location.url('/...?reload=false');. That way it's explicit, and there's no weird errors if the templateUrl or the controller happen to be the same on another route.
  • Joe Hanink
    Joe Hanink almost 11 years
    is this sort of thing advisable? It sounds like it violates an expectation of the current route design. Are there any side-effects or gotchas to this? Do you expect this to be valid in future releases? You might want to get in touch with the angular team on google plus and get their opinion.
  • tanguy_k
    tanguy_k almost 11 years
    Well it is a hack... use with caution. What is nice here is that the hackish code does not pollute your controllers, just one line: DoNotReloadCurrentTemplate($scope)
  • Scott Sword
    Scott Sword over 9 years
    That is not true. It only prevents reload for query params, not hash/route updates. All updates made via $location.path or $location.url go through the $route service triggering a view refresh--hence all the hackish solutions presented on this page to mitigate it.
  • Scott Sword
    Scott Sword over 9 years
    This really should be configurable from within the framework.
  • Hashbrown
    Hashbrown over 8 years
    you would still need to watch for $locationChangeError (fired when defer.reject() occurs) inside ArticleCtrl to make changes to the page based on the now-changed url
  • Bigood
    Bigood over 8 years
    @MalucoMarinero $$route.controller doesn't exist anymore, at least as of 1.4.8.
  • Highdown
    Highdown over 8 years
    After spending a couple of days researching and trying different approaches, I came across your answer. Worked like a charm using the normal AngularJS router, which was important to us. Verified using AngularJS v1.3.12 on Windows/MAC/iOS mobile, tablets, and PCs.
  • AnthumChris
    AnthumChris over 7 years
    ehmicky's solution works fine. See docs.angularjs.org/api/ngRoute/provider/$routeProvider#when "[reloadOnSearch=true] - {boolean=} - reload route when only $location.search() or $location.hash() changes."
  • doublesharp
    doublesharp about 7 years
    While the route was not changing I was having a problem with my controller reloading and was able to fix it by modifying $locationChangeSuccess to use the event and added event.preventDefault() on the first line.