AngularJS:在不完全重新加载控制器的情况下更改哈希和路由

时间:2012-08-24 19:06:17

标签: controller reload routes angularjs persist

我有一个控制器,路径如下:

#/物品/ 1234

我想在不完全重新加载控制器的情况下更改路径,因此我可以保持控制器中其他内容的位置不变(列出滚动)

我可以想到几种方法来做到这一点,但它们都非常难看。做这样的事情有最好的做法吗?我尝试使用reloadOnSearch:false,但它似乎没有改变任何东西。

10 个答案:

答案 0 :(得分:34)

有同样的挑战,

another StackOverflow response发现了一个黑客攻击

相当干净的解决方案 - 我所做的就是将这些行添加到设置$ location.path的控制器中:

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

..并确保$ route注入控制器当然。

但是,感觉像“DoNotFollowRoutesOnPathChange”是AngularJS中缺少的一个功能。

/延

更新:由于有效地监听此事件会导致$ routeProvider配置的进一步使用,我不得不将此捕获限制为仅当前路径:

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

丑陋......

答案 1 :(得分:21)

简要回答:

您可以使用前面提到的$location.search()方法。您应该听取范围上的"$routeUpdate"事件而不是其他路径事件。 $route API

说明:

  1. 首先(您已经知道),将reloadOnSearch: false添加到$routeProvider

    $routeProvider.when('/somewhere', {
        controller: 'SomeCtrl',
        reloadOnSearch: false
    })
    
  2. 将您的锚标记hrefng-href更改为href="#/somewhere?param=value"如果路径部分($routeChangeSuccess)不是,则会触发/somewhere事件与当前位置相同。否则会触发$routeUpdate事件。

  3. 监听范围事件:

    $scope.$on("$routeUpdate", function(event, route) {
        // some code here
    });
    
  4. 如果要更改代码中的搜索参数,可以使用$location.search()方法。 $location.search API

答案 2 :(得分:8)

如果将reloadOnSearch设置为false,则可以设置url的?a=b&c=d部分而不重新加载。但是,如果没有重新加载,您无法巧妙地更改实际位置。

答案 3 :(得分:7)

修改

使用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;
}]);

使用方法:

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

旧答案

我根据Jens X Augustsson的回答写了一个可重复使用的工厂:

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;
      }
    });
  };
}]);

适用于AngularJS 1.0.6

使用方法:

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

  DoNotReloadCurrentTemplate($scope);
}]);

AngularJS问题在这里:https://github.com/angular/angular.js/issues/1699

答案 4 :(得分:1)

定义一个工厂来处理HTML5的window.history就像这样(注意我保留一个自己的Stack,以使它在android上工作,因为它有一些问题):

.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
        }
    }
})

在您的依赖范围上添加一些侦听器,你会很好:

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

多数民众赞成我是怎么做到的。 在我看来,URL只应在加载新视图时更改。 我认为这就是Angular团队的意图。 如果要在模型上映射前进/后退按钮,请尝试使用HTML5的window.history

希望我能提供帮助。 干杯,海因里希

答案 5 :(得分:1)

使用:

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

这样可以防止在查询参数更改时重新加载控制器,还可以防止哈希更改。

答案 6 :(得分:1)

如果你在2015年登陆这里:真正的答案是不使用这些黑客(我敢这么说,因为通过使用上面列出的任何方法,你将失去使用决心和喜欢的可能性)但切换到ui-router

Here's关于差异的方便介绍。实现应该像为$ state交换$ route并将状态转换为名称一样简单。

我正在切换到一种方法,我将使用一个href引用一个路由,并带有一个可选的get参数,该参数可以在不重新加载状态的情况下更改状态。有关此问题的更多信息,请查看“参数”部分here

答案 7 :(得分:0)

这个简单的解决方案(令人惊讶)对我有用:

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

虽然它不会阻止状态在后退和前进按钮上重新加载。 注意:我使用的是angularjs 1.2.7和ui-router 0.0.1(我知道它已经老了)。

答案 8 :(得分:0)

这是插件:https://github.com/anglibs/angular-location-update

用法:

$location.update_path('/notes/1');

答案 9 :(得分:0)

您可以使用$locationChange~ resolve承诺选项在HistoryStateroute黑客攻击的情况下执行此操作。

假设您有一条article路线,其中该号码已更改,您可以执行此操作;

$routeProvider.when(
    '/article/:number',
    {
        templateUrl : 'partial.html',
        controller : 'ArticleCtrl',
        resolve : {
            load : ['$q', '$routeParams', function($q, $routeParams) {
                var defer = $q.defer();

                //number was specified in the previous route parameters, means we were already on the article page
                if ($routeParams.number)
                    //so dont reload the view
                    defer.reject('');
                //otherwise, the number argument was missing, we came to this location from another route, load the view
                else
                    defer.resolve();

                return defer.promise;
            }]
        }
    }
);