可以将文章内容翻译成中文,广告屏蔽插件可能会导致该功能失效(如失效,请关闭广告屏蔽插件后再试):
问题:
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.
回答1:
Had the very same challange,
Found a hack in another StackOverflow response that did the trick
Fairly clean solution - all I did was to add these lines to the controller that sets $location.path:
var lastRoute = $route.current;
$scope.$on('$locationChangeSuccess', function(event) {
$route.current = lastRoute;
});
..and made sure $route in injected into the controller of course.
But still, feels like "DoNotFollowRoutesOnPathChange" is a missing feature in AngularJS.
/Jens
Update: Since listening to this event effectively kills further usage of $routeProvider configs, I had to limit this catch to current path only:
var lastRoute = $route.current;
if ($route.current.$route.templateUrl.indexOf('mycurrentpath') > 0) {
$route.current = lastRoute;
}
Getting ugly...
回答2:
Brief Answer:
You can use the $location.search()
method as you mentioned. You should listen to the "$routeUpdate"
event on scope instead of other route events. $route API.
Explanation:
First of all (you already know), add reloadOnSearch: false
to your $routeProvider
:
$routeProvider.when('/somewhere', {
controller: 'SomeCtrl',
reloadOnSearch: false
})
Change your anchor tag href
or ng-href
to href="#/somewhere?param=value"
this will trigger $routeChangeSuccess
event if the path part (/somewhere
) is not the same as current location. Otherwise it will trigger $routeUpdate
event.
Listen event on scope:
$scope.$on("$routeUpdate", function(event, route) {
// some code here
});
If you want to change search params in code, you can use $location.search()
method. $location.search API.
回答3:
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.
回答4:
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:
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
回答6:
Use:
$routeProvider.when('/somewhere', {
controller: 'SomeCtrl',
reloadOnSearch: false
})
This will prevent reloading the controller on query parameter change, but also on hash change.
回答7:
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
回答8:
This simple solution (surprisingly) works for me:
$state.params.id = id; //updating the $state.params somehow prevents reloading the state
$location.path('/articles/' + id);
It doesn't prevent the state reloading on Back and forward button though.
Note: I'm using angularjs 1.2.7 and ui-router 0.0.1 (I know it's old).
回答9:
Here is plugin: https://github.com/anglibs/angular-location-update
Usage:
$location.update_path('/notes/1');
回答10:
You can do this without the $locationChange~
and HistoryState
hacks using route
s resolve
promise option.
Assuming you had an article
route where that number is what changed you could do this;
$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;
}]
}
}
);