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

Posted

技术标签:

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

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

#/articles/1234

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

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

【问题讨论】:

【参考方案1】:

有同样的挑战,

在another *** 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.

解释:

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

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

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

    监听范围内的事件:

    $scope.$on("$routeUpdate", function(event, route) 
        // some code here
    );
    

    如果要更改代码中的搜索参数,可以使用$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 对页面进行更改

以上是关于AngularJS:在不完全重新加载控制器的情况下更改哈希和路由的主要内容,如果未能解决你的问题,请参考以下文章

angularjs如何在不更改url的情况下加载另一个控制器

如何在不关闭和重新加载应用程序的情况下获得新的 JSON 响应

AngularJS、ocLazyLoad 和 ui-router:如何在不将“状态”集中在 app.js 主类上的情况下按需加载

有没有办法在不删除视图并重新添加的情况下更新 StackView 中的视图?

如何在不重新加载图像的情况下重新加载 collectionview 单元格

如何在不重新加载整个页面的情况下重新加载 div?