Skip to content
This repository has been archived by the owner on Apr 12, 2024. It is now read-only.

Option on $location to allow hash/path change w/o reloading the route #1699

Closed
cgross opened this issue Dec 12, 2012 · 194 comments
Closed

Option on $location to allow hash/path change w/o reloading the route #1699

cgross opened this issue Dec 12, 2012 · 194 comments

Comments

@cgross
Copy link

cgross commented Dec 12, 2012

We have a paradigm in our app that user's create new things on the same page that they view things. So our route is like /thing/:id. When creating a new thing they go to /thing/new. Once the thing has been successfully saved we want to change the route to /thing/1234 (or whatever its new id is). Now the partial doesnt need to be reloaded because the data is all the same. We just want the path to be updated so a user can now bookmark the correct link, etc.

Having an option on $location (not on the route definition) to enable/disable route loading would work but I'm sure there are other ways to implement the feature.

@qualidafial
Copy link

+1, having the same issue.

@qualidafial
Copy link

@cgross Since the $location service uses JQuery-style method chaining, and all commands are deferred until $digest anyway, I suggest adding a new chaining method, e.g. reload(boolean):

$location.path("/thing/"+thing.id).replace().reload(false)

P.S. Is this the Chris Gross that started the Eclipse Nebula project?

@cgross
Copy link
Author

cgross commented Dec 17, 2012

Yup :)

@celmaun
Copy link

celmaun commented Dec 18, 2012

+1 👍

@shahmirn
Copy link

+1

Right now, we're using a dirty hack to get around this problem.

    $scope.$on('$locationChangeSuccess', function(event) {

        // Want to prevent re-loading when going from /dataEntry/1 to some other dataEntry path
        if ($route && $route.current && $route.current.$route.templateUrl.indexOf('dataEntry') > 0) {
            $route.current = lastRoute; //Does the actual prevention of routing
        }
});

@stryderjzw
Copy link

+1

4 similar comments
@chicoxyzzy
Copy link

+1

@evrijkom
Copy link

evrijkom commented Feb 4, 2013

+1

@tnajdek
Copy link

tnajdek commented Mar 18, 2013

+1

@sylvain-hamel
Copy link

+1

@sylvain-hamel
Copy link

Here is my attempt at fixing this by supporting preventDefault() for $routeChangeStart. What do you think about that?
Commit : sylvain-hamel@f8ac46e

lrlopez added a commit to lrlopez/angular.js that referenced this issue Apr 13, 2013
This adds a new method `notify` to `$location` that allows updating the
location without triggering `$locationChangeStart`/`$locationChangeSuccess`
events.

The method is chainable and must be called with a boolean parameter. Any
falsy value will disable the notification procedure and will block any
route update that may apply.

The skip flag will be reset after any digest cycle, so it must be set again
on any subsequent change if needed.

Example: `$location.path('/client/2').replace().notify(false);`

Closes angular#1699
@lrlopez
Copy link
Contributor

lrlopez commented Apr 13, 2013

I've been working into @qualidafial approach for a few days and now the PR is ready. I've created a new notify method that allows skipping the location change notification for one time. This should avoid triggering any route change or reload.

This doesn't touch the routing system, so you don't have to change your route definitions. It will still work even if you use ui-router instead of the standard AngularJS routing system.

Example: $location.path('/client/2').replace().notify(false);

I've also worked some tests and documentation bits.

@cburgdorf
Copy link
Contributor

+1

@tiff
Copy link

tiff commented Apr 17, 2013

another +1
Using @lrlopez' pull request already. Works like a charm.

@cayblood
Copy link

+1

@mlegenhausen
Copy link

+1

@lrlopez
Copy link
Contributor

lrlopez commented Apr 28, 2013

Unfortunately PR #2398 has been rejected by the Angular team as skipping notifications could lead to inconsistencies between the actual URL and the current route. There is a long explanation at the end of the discussion.

I don't need this feature in any of my projects, but I'll keep it in my repository so it can be merged if you wish. Just let me know if it gets out of sync with the master branch so I can rebase the changes. Thanks!

@camshaft
Copy link

+1

@keabard
Copy link

keabard commented Jun 4, 2013

+1, need this :(

@olanod
Copy link

olanod commented Jun 19, 2013

+1

@tkrotoff
Copy link

Edit: better approach here: #1699 (comment)

I've created a reusable factory to bypass this (based on the idea from @shahmirn):

/**
 * HACK Do not reload the current template if it is not needed.
 *
 * See AngularJS: Change hash and route without completely reloading controller http://stackoverflow.com/questions/12115259/angularjs-change-hash-and-route-without-completely-reloading-controller
 */
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 not reloading template: ' + $route.current.$$route.templateUrl);
        $route.current = lastRoute;
      }
    });
  };
}]);

How to use:

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

  DoNotReloadCurrentTemplate($scope);
}]);

Source: http://stackoverflow.com/a/16496112/990356

@Zariel
Copy link

Zariel commented Jul 10, 2013

If you set "reloadOnSearch" to false on the route then it seems to fix the hash changes reloading the whole page.

@mgcrea
Copy link
Contributor

mgcrea commented Jul 11, 2013

+1

This would be great for mobile apps, where the ng-view create/destroy lifecycle is not appropriate (kills performance and usability).

@braaam
Copy link

braaam commented Jul 29, 2013

@tkrotoff Thanks for the example. Since your workaround is cancelling event notifications I've added a broadcast on the rootscope so other controllers can still be notified of the route change:

/**
 * HACK Do not reload the current template if it is not needed.
 *
 * See AngularJS: Change hash and route without completely reloading controller http://stackoverflow.com/questions/12115259/angularjs-change-hash-and-route-without-completely-reloading-controller
 */
app.factory('DoNotReloadCurrentTemplate', ['$route', '$rootScope', function($route, $rootScope) {
  return function(scope) {
    var lastRoute = $route.current;
    scope.$on('$locationChangeSuccess', function() {
      if (lastRoute.$$route.templateUrl === $route.current.$$route.templateUrl) {
        console.log('DoNotReloadCurrentTemplate not reloading template: ' + $route.current.$$route.templateUrl);
        $rootScope.$broadcast('locationChangedWithoutReload', $route.current);
        $route.current = lastRoute;        
      }
    });
  };
}]);

Then:

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

  DoNotReloadCurrentTemplate($scope);

  $rootScope.$on('locationChangedWithoutReload', function(event, route) {
    // set location specific breadcrumbs
    $scope.breadcrumbs = assembleBreadCrumbs();

    // do something for specific route
    if (route.$$route.action == 'item') {
      $scope.item = $scope.items[params.itemId];
      $rootScope.title = $scope.item.name;
    }
  });
}]);

@sergey-buturlakin
Copy link

I have the same issue as the topic author has and created modified version of solution described above.

app.factory('skipReload', [
    '$route',
    '$rootScope',
    function ($route, $rootScope) {
        return function () {
            var lastRoute = $route.current;
            var un = $rootScope.$on('$locationChangeSuccess', function () {
                $route.current = lastRoute;
                un();
            });
        };
    }
]);

Usage:

app.controller('ThingCtrl', ['$scope', '$http', '$location', 'skipReload', function ($scope, $http, $location, skipReload) {
    ...
    $scope.submit = function () {
        ...
        $http.post('thing', thing).success(function (id) {
            skipReload();
            $location.path('/thing/' + id).replace();
        });
    };
}]);

Using permanent event listener that tests templateUrl to decide whether to allow route reload, could break routing to other views that uses same templateUrl. For example, I routed to /thing/new and the view shows link to last added thing /thing/5, I'll be unable to navigate to thing 5 and to any other thing (even changing url in browser explicitly). Another problem with templateUrl testing is that templates could be specified by using template property (as string or function).

This hack allows to skip exactly one route reload and doesn't break routing.

@petebacondarwin petebacondarwin modified the milestones: 1.6.x, 1.5.x - migration-facilitation Nov 23, 2015
@vadimcoder
Copy link

+1

@0cv
Copy link

0cv commented Dec 8, 2015

I confirm that ngSilent is doing perfectly the job. Even so an official solution would be highly appreciated.

@freeman14
Copy link

The solution is essentially to add an extra parameter to $location.path().

app.run(['$route', '$rootScope', '$location', function ($route, $rootScope, $location) {
    var original = $location.path;
    $location.path = function (path, reload) {
        if (reload === false) {
            var lastRoute = $route.current;
            var un = $rootScope.$on('$locationChangeSuccess', function () {
                $route.current = lastRoute;
                un();
            });
        }
        return original.apply($location, [path]);
    };
}]);

And then just use like this

$location.path('/path/that/you/need', false);

@JohnGalt1717
Copy link

I should just be able to do $location.replace() just like the window.location.replace function. This is just silly because every Create in a CRUD operation will always have this issue.

@stitisami
Copy link

(y) +1

@espeandr
Copy link

espeandr commented Apr 6, 2016

+1

@yalishizhude
Copy link

yalishizhude commented May 29, 2016

I advise you use "search param" like this:
/thing?id=1234
Do not config the param in the router provider,it won't reload view and controller.
If you want to get id
$location.search().id //1234
@cgross

@dolymood
Copy link

dolymood commented Jun 2, 2016

Demo http://codepen.io/dolymood/details/jrEQBx/
Use decorator to decorator ngViewDirective

@mattantonelli
Copy link

+1

2 similar comments
@srijanshukla18
Copy link

+1

@Meokme
Copy link

Meokme commented Aug 3, 2016

+1

gkalpak added a commit to gkalpak/angular.js that referenced this issue Aug 7, 2016
Enables users to specify that a particular route should not be reloaded after a URL change
(including a change in `$location.path()`), if the new URL maps to the same route.
The default behavior is still to always load the matched route when any part of the URL changes.

Related to angular#1699, angular#5860, angular#14999 (potentially closing the first two).
@bytexro
Copy link

bytexro commented Aug 30, 2016

+1

2 similar comments
@b-mcrae
Copy link

b-mcrae commented Oct 3, 2016

+1

@nitinsurana
Copy link

+1

@risingfish
Copy link

+!

@angular angular locked and limited conversation to collaborators Nov 28, 2016
@petebacondarwin
Copy link
Member

Enough of the +1s already :-)
We get the message. We will look at this before Xmas!

@Narretz Narretz modified the milestones: 1.6.x, 1.7.x Apr 12, 2018
@gkalpak gkalpak self-assigned this May 9, 2018
@petebacondarwin petebacondarwin self-assigned this May 14, 2018
@gkalpak
Copy link
Member

gkalpak commented Jun 8, 2018

Closed by #15002.

@gkalpak gkalpak closed this as completed Jun 8, 2018
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.