It's been asked before, and from the answers it doesn't look good. I'd like to ask with this sample code in consideration...
My app loads the current item in the service that provides it. There are several controllers that manipulate the item data without the item being reloaded.
My controllers will reload the item if it's not set yet, otherwise, it will use the currently loaded item from the service, between controllers.
Problem: I would like to use different paths for each controller without reloading Item.html.
1) Is that possible?
2) If that is not possible, is there a better approach to having a path per controller vs what I came up with here?
app.js
var app = angular.module('myModule', []).
config(['$routeProvider', function($routeProvider) {
$routeProvider.
when('/items', {templateUrl: 'partials/items.html', controller: ItemsCtrl}).
when('/items/:itemId/foo', {templateUrl: 'partials/item.html', controller: ItemFooCtrl}).
when('/items/:itemId/bar', {templateUrl: 'partials/item.html', controller: ItemBarCtrl}).
otherwise({redirectTo: '/items'});
}]);
Item.html
<!-- Menu -->
<a id="fooTab" my-active-directive="view.name" href="#/item/{{item.id}}/foo">Foo</a>
<a id="barTab" my-active-directive="view.name" href="#/item/{{item.id}}/bar">Bar</a>
<!-- Content -->
<div class="content" ng-include="" src="view.template"></div>
controller.js
// Helper function to load $scope.item if refresh or directly linked
function itemCtrlInit($scope, $routeParams, MyService) {
$scope.item = MyService.currentItem;
if (!$scope.item) {
MyService.currentItem = MyService.get({itemId: $routeParams.itemId});
$scope.item = MyService.currentItem;
}
}
function itemFooCtrl($scope, $routeParams, MyService) {
$scope.view = {name: 'foo', template: 'partials/itemFoo.html'};
itemCtrlInit($scope, $routeParams, MyService);
}
function itemBarCtrl($scope, $routeParams, MyService) {
$scope.view = {name: 'bar', template: 'partials/itemBar.html'};
itemCtrlInit($scope, $routeParams, MyService);
}
Resolution.
Status: Using search query as recommended in the accepted answer allowed me to provide different urls without reloading the main controller.
app.js
var app = angular.module('myModule', []).
config(['$routeProvider', function($routeProvider) {
$routeProvider.
when('/items', {templateUrl: 'partials/items.html', controller: ItemsCtrl}).
when('/item/:itemId/', {templateUrl: 'partials/item.html', controller: ItemCtrl, reloadOnSearch: false}).
otherwise({redirectTo: '/items'});
}]);
Item.html
<!-- Menu -->
<dd id="fooTab" item-tab="view.name" ng-click="view = views.foo;"><a href="#/item/{{item.id}}/?view=foo">Foo</a></dd>
<dd id="barTab" item-tab="view.name" ng-click="view = views.bar;"><a href="#/item/{{item.id}}/?view=foo">Bar</a></dd>
<!-- Content -->
<div class="content" ng-include="" src="view.template"></div>
controller.js
function ItemCtrl($scope, $routeParams, Appts) {
$scope.views = {
foo: {name: 'foo', template: 'partials/itemFoo.html'},
bar: {name: 'bar', template: 'partials/itemBar.html'},
}
$scope.view = $scope.views[$routeParams.view];
}
directives.js
app.directive('itemTab', function(){
return function(scope, elem, attrs) {
scope.$watch(attrs.itemTab, function(val) {
if (val+'Tab' == attrs.id) {
elem.addClass('active');
} else {
elem.removeClass('active');
}
});
}
});
The content inside my partials are wrapped with ng-controller=...
If you don't have to use URLs like #/item/{{item.id}}/foo and #/item/{{item.id}}/bar but #/item/{{item.id}}/?foo and #/item/{{item.id}}/?bar instead, you can set up your route for /item/{{item.id}}/ to have reloadOnSearch set to false (docs.angularjs.org/api/ngRoute.$routeProvider). That tells AngularJS to not reload the view if the search part of the url changes.
If you need to change the path, add this after your .config in your app file.
Then you can do $location.path('/sampleurl', false); to prevent reloading
app.run(['$route', '$rootScope', '$location', function ($route, $rootScope, $location) {
var original = $location.path;
$location.path = function (path, reload) {
if (reload === false) {
var lastRoute = $route.current;
var un = $rootScope.$on('$locationChangeSuccess', function () {
$route.current = lastRoute;
un();
});
}
return original.apply($location, [path]);
};
}])
Credit goes to http://joelsaupe.com/programming/angularjs-change-path-without-reloading/ for the most elegant solution I've found.
Though this post is old and has had an answer accepted, using reloadOnSeach=false does not solve the problem for those of us who need to change actual path and not just the params. Here's a simple solution to consider:
Use ng-include instead of ng-view and assign your controller in the template.
<!-- In your index.html - instead of using ng-view -->
<div ng-include="templateUrl"></div>
<!-- In your template specified by app.config -->
<div ng-controller="MyController">{{variableInMyController}}</div>
//in config
$routeProvider
.when('/my/page/route/:id', {
templateUrl: 'myPage.html',
})
//in top level controller with $route injected
$scope.templateUrl = ''
$scope.$on('$routeChangeSuccess',function(){
$scope.templateUrl = $route.current.templateUrl;
})
//in controller that doesn't reload
$scope.$on('$routeChangeSuccess',function(){
//update your scope based on new $routeParams
})
Only down-side is that you cannot use resolve attribute, but that's pretty easy to get around. Also you have to manage the state of the controller, like logic based on $routeParams as the route changes within the controller as the corresponding url changes.
Here's an example: http://plnkr.co/edit/WtAOm59CFcjafMmxBVOP?p=preview
why not just put the ng-controller one level higher,
<body ng-controller="ProjectController">
<div ng-view><div>
And don't set controller in the route,
.when('/', { templateUrl: "abc.html" })
it works for me.
For those who need path() change without controllers reload - Here is plugin: https://github.com/anglibs/angular-location-update
Usage:
$location.update_path('/notes/1');
Based on http://stackoverflow.com/a/24102139/1751321
P.S. This solution http://stackoverflow.com/a/24102139/1751321 contains bug after path(, false) called - it will break browser navigation back/forward until path(, true) called