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
) 与当前位置不同,则将您的锚标记 href
或 ng-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
黑客的情况下使用 route
s 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 中的视图?