AngularJS: Change hash and route without completel

2019-01-16 10:20发布

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.

10条回答
一纸荒年 Trace。
2楼-- · 2019-01-16 10:32

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

查看更多
趁早两清
3楼-- · 2019-01-16 10:36

define a factory to handle HTML5's window.history like so (note I keep an own Stack to make it work on android as well since it has a few issues there):

.factory('History', function($rootScope) {
    var StateQueue = [];
    var StatePointer = 0;
    var State = undefined;
    window.onpopstate = function(){
        // called when back button is pressed
        State = StateQueue.pop();
        State = (State)?State:{};
        StatePointer = (StatePointer)?StatePointer-1:0;
        $rootScope.$broadcast('historyStateChanged', State);
        window.onpopstate = window.onpopstate;

    }
    return {
        replaceState : function(data, title, url) {
            // replace current state
            var State = this.state();
            State = {state : data};
            window.history.replaceState(State,title,url);
            StateQueue[StatePointer] = State;
            $rootScope.$broadcast('historyStateChanged', this.state());
        },
        pushState : function(data, title, url){
            // push state and increase pointer
            var State = this.state();
            State = {state : data};
            window.history.pushState(State,title,url);
            StateQueue.push(State);
            $rootScope.$broadcast('historyStateChanged', this.state());
            StatePointer ++;
        },
        fakePush : function(data, title, url) {
            // call this when you do location.url(url)
            StateQueue.push((StateQueue.length - 1 >= 0)?StateQueue[StateQueue.length -1]:{});
            StatePointer ++;
            $rootScope.$broadcast('historyStateChanged', this.state());
        },
        state : function() {
            // get current state
            return (StateQueue[StatePointer])?StateQueue[StatePointer]:{};
        },
        popState : function(data) {
            // TODO manually popState
        },
        back : function(data) {
            // TODO needed for iphone support since iphone doesnt have a back button
        }
    }
})

add a few listeners on your dependent scopes and you will be fine like so:

$scope.$on('historyStateChanged', function(e,v) {
        if(v)
            $scope.state = v;
        else
            $scope.state = {}
    });

thats how I do it. In my point of view the URL should only change when a new view is loaded. I think thats how the Angular Team intended it anyways. If you want to map forward/back button on your model try to do it with HTML5's window.history

Hope I could help. Cheers, Heinrich

查看更多
4楼-- · 2019-01-16 10:37

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

查看更多
对你真心纯属浪费
5楼-- · 2019-01-16 10:38

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.

查看更多
The star\"
6楼-- · 2019-01-16 10:38

Use:

$routeProvider.when('/somewhere', {
    controller: 'SomeCtrl',
    reloadOnSearch: false
})

This will prevent reloading the controller on query parameter change, but also on hash change.

查看更多
Root(大扎)
7楼-- · 2019-01-16 10:44

Here is plugin: https://github.com/anglibs/angular-location-update

Usage:

$location.update_path('/notes/1');
查看更多
登录 后发表回答