【问题标题】:AngularJS: Change hash and route without completely reloading controllerAngularJS:在不完全重新加载控制器的情况下更改哈希和路由
【发布时间】:2012-08-20 08:43:49
【问题描述】:

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

#/articles/1234

我想在不完全重新加载控制器的情况下更改路线,这样我可以保持控制器中其他东西的位置不变(滚动的列表)

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

【问题讨论】:

    标签: controller reload routes angularjs persist


    【解决方案1】:

    有同样的挑战,

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

    变丑了……

    【讨论】:

    • 我将这个设置用于我正在做的事情,我认为你最好听听 $route.current.$$route.controller,即$route.current.$$route.controller == 'controllerName' 这比更可靠一点玩路径游戏。
    • 在尝试让它在我的项目中工作后,我决定收听$route.current.params.reload == 'false',在那里我导致$location 更改发生在$location.url('/...?reload=false'); 之类的东西上。这样它就很明确了,如果 templateUrl 或控制器在另一条路由上碰巧相同,也不会出现奇怪的错误。
    • 这确实应该可以在框架内进行配置。
    • @MalucoMarinero $$route.controller 不再存在,至少从 1.4.8 开始。
    【解决方案2】:

    简要回答:

    您可以使用您提到的$location.search() 方法。您应该在作用域上收听"$routeUpdate" 事件,而不是其他路由事件。 $route API.

    解释:

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

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

    3. 监听范围内的事件:

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

    【讨论】:

    • 在花了几天时间研究和尝试不同的方法后,我发现了你的答案。使用普通的 AngularJS 路由器就像一个魅力,这对我们很重要。在 Windows/MAC/iOS 移动设备、平板电脑和 PC 上使用 AngularJS v1.3.12 验证。
    【解决方案3】:

    如果您将 reloadOnSearch 设置为 false,您可以在不重新加载的情况下设置 url 的 ?a=b&c=d 部分。但是,如果不重新加载,您无法漂亮地更改实际位置。

    【讨论】:

    • 我可以在不重新加载整个页面的情况下更改查询参数吗?如在哈希之前?或者你的意思是如果我有 ?a=b&c=d 在最后,在哈希之后?
    • 是的,你可以用$location.search('a','b');更改查询参数
    • 嗯...我不知道这是否适合这个应用程序,我宁愿与哈希保持一致,这是最终的路线。我正在考虑有一个控制器作为路由的路径,它将发送事件以显示和隐藏实际的控制器,并且只有在第一次加载时才完全更新它。不过这听起来很丑陋,所以我不确定这是否是最好的解决方案。
    【解决方案4】:

    编辑

    使用 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

    【讨论】:

    • 这种事情可取吗?听起来它违反了对当前路线设计的期望。这有什么副作用或陷阱吗?您希望这在未来的版本中有效吗?您可能想与 google plus 上的 Angular 团队取得联系并听取他们的意见。
    • 嗯,这是一个黑客......谨慎使用。这里的好处是,hackish 代码不会污染您的控制器,只需一行:DoNotReloadCurrentTemplate($scope)
    • 虽然路线没有改变,但我的控制器重新加载时出现问题,并且能够通过修改$locationChangeSuccess 以使用event 并在第一行添加event.preventDefault() 来修复它。
    【解决方案5】:

    定义一个工厂来处理 HTML5 的 window.history 像这样(注意我保留了一个自己的堆栈以使其在 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

    希望我能帮上忙。 干杯,海因里希

    【讨论】:

      【解决方案6】:

      用途:

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

      这将防止在查询参数更改以及哈希更改时重新加载控制器。

      【讨论】:

      • 这不是真的。它只防止重新加载查询参数,而不是哈希/路由更新。通过 $location.path 或 $location.url 进行的所有更新都会通过 $route 服务触发视图刷新——因此,此页面上提供的所有黑客解决方案都可以缓解它。
      • ehmicky 的解决方案运行良好。见docs.angularjs.org/api/ngRoute/provider/$routeProvider#when“[reloadOnSearch=true] - {boolean=} - 当只有 $location.search() 或 $location.hash() 改变时重新加载路由。”
      【解决方案7】:

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

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

      我目前正在切换到一种方法,在该方法中我将使用 a href 引用路由,并带有一个可选的 get 参数,该参数可以更改状态而不重新加载它。有关这方面的更多信息,请查看“参数”部分 here

      【讨论】:

        【解决方案8】:

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

        $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(我知道它很旧)。

        【讨论】:

          【解决方案9】:

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

          用法:

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

          【讨论】:

            【解决方案10】:

            您可以在没有 $locationChange~HistoryState 黑客的情况下使用 routes resolve 承诺选项来做到这一点。

            假设您有一个 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;
                        }]
                    }
                }
            );
            

            【讨论】:

            • 您仍然需要注意 $locationChangeError(在出现 defer.reject() 时触发)在 ArticleCtrl 中根据现在更改的 url 对页面进行更改
            猜你喜欢
            • 1970-01-01
            • 2014-06-28
            • 2013-02-05
            • 1970-01-01
            • 1970-01-01
            • 1970-01-01
            • 1970-01-01
            • 2015-10-30
            • 2014-02-03
            相关资源
            最近更新 更多