Можете ли вы изменить путь без перезагрузки контроллера в AngularJS?
Его спрашивали раньше, и из ответов он выглядит не очень хорошо. Я бы хотел спросить с этим примером кода...
Мое приложение загружает текущий элемент в службу, которая его предоставляет. Существует несколько контроллеров, которые манипулируют данными элемента без перезагрузки элемента.
Мои контроллеры перезагружают элемент, если он еще не установлен, в противном случае он будет использовать загруженный в данный момент элемент из службы между контроллерами.
Проблема. Я хотел бы использовать разные пути для каждого контроллера без перезагрузки Item.html.
1) Возможно ли это?
2) Если это невозможно, есть ли лучший подход к тому, чтобы иметь путь на контроллер по сравнению с тем, что я здесь придумал?
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);
}
Разрешение.
Статус. Использование поискового запроса, как рекомендовано в принятом ответе, позволило мне предоставить разные URL-адреса без перезагрузки основного контроллера.
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');
}
});
}
});
Содержимое внутри моих партикулов завернуто ng-controller=...
Ответы
Ответ 1
Если вам не нужно использовать URL-адреса, такие как #/item/{{item.id}}/foo
и #/item/{{item.id}}/bar
но #/item/{{item.id}}/?foo
и #/item/{{item.id}}/?bar
вместо этого, вы можете настроить свой маршрут для /item/{{item.id}}/
иметь reloadOnSearch
установлен в false
(docs.angularjs.org/api/ngRoute. $ routeProvider). Это говорит AngularJS не перезагружать представление, если изменяется поисковая часть URL.
Ответ 2
Если вам нужно изменить путь, добавьте его после вашего .config в файл приложения. Затем вы можете сделать $location.path('/sampleurl', false);
, чтобы предотвратить перезагрузку
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]);
};
}])
Кредит отправляется https://www.consolelog.io/angularjs-change-path-without-reloading для самого элегантного решения, которое я нашел.
Ответ 3
почему бы просто не поставить ng-контроллер на один уровень выше,
<body ng-controller="ProjectController">
<div ng-view><div>
И не устанавливайте контроллер на маршруте,
.when('/', { templateUrl: "abc.html" })
он работает для меня.
Ответ 4
Для тех, кому нужно изменить path() без перезагрузки контроллеров - вот плагин: https://github.com/anglibs/angular-location-update
Использование:
$location.update_path('/notes/1');
Основано на fooobar.com/questions/38224/...
PS Это решение fooobar.com/questions/38224/... содержит ошибку после вызова пути (, false) - оно будет нарушать навигацию браузера вперед/назад до вызова пути (, true)
Ответ 5
Хотя это сообщение устарело и приняло ответ, использование reloadOnSeach = false не решает проблему для тех из нас, кому нужно изменить фактический путь, а не только параметры. Здесь простое решение:
Используйте ng-include вместо ng-view и назначьте свой контроллер в шаблоне.
<!-- 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
})
Только сторонняя сторона заключается в том, что вы не можете использовать атрибут разрешения, но это довольно легко обойти. Также вам нужно управлять состоянием контроллера, например, логикой на основе $routeParams, поскольку маршрут изменяется в контроллере по мере изменения соответствующего URL-адреса.
Вот пример: http://plnkr.co/edit/WtAOm59CFcjafMmxBVOP?p=preview
Ответ 6
Я использую это решение
angular.module('reload-service.module', [])
.factory('reloadService', function($route,$timeout, $location) {
return {
preventReload: function($scope, navigateCallback) {
var lastRoute = $route.current;
$scope.$on('$locationChangeSuccess', function() {
if (lastRoute.$$route.templateUrl === $route.current.$$route.templateUrl) {
var routeParams = angular.copy($route.current.params);
$route.current = lastRoute;
navigateCallback(routeParams);
}
});
}
};
})
//usage
.controller('noReloadController', function($scope, $routeParams, reloadService) {
$scope.routeParams = $routeParams;
reloadService.preventReload($scope, function(newParams) {
$scope.routeParams = newParams;
});
});
Этот подход сохраняет функциональность кнопки "Назад", и в шаблоне всегда есть текущие параметры маршрута, в отличие от некоторых других подходов, которые я видел.
Ответ 7
Здесь мое более полное решение, которое решает несколько вещей, которые @Vigrond и @rahilwazir пропустили:
- Когда параметры поиска были изменены, это предотвратит трансляцию
$routeUpdate
.
- Когда маршрут фактически оставлен без изменений,
$locationChangeSuccess
никогда не запускается, что приводит к предотвращению следующего обновления маршрута.
-
Если в том же цикле дайджеста был еще один запрос на обновление, на этот раз, желая перезагрузить, обработчик события отменит перезагрузку.
app.run(['$rootScope', '$route', '$location', '$timeout', function ($rootScope, $route, $location, $timeout) {
['url', 'path'].forEach(function (method) {
var original = $location[method];
var requestId = 0;
$location[method] = function (param, reload) {
// getter
if (!param) return original.call($location);
# only last call allowed to do things in one digest cycle
var currentRequestId = ++requestId;
if (reload === false) {
var lastRoute = $route.current;
// intercept ONLY the next $locateChangeSuccess
var un = $rootScope.$on('$locationChangeSuccess', function () {
un();
if (requestId !== currentRequestId) return;
if (!angular.equals($route.current.params, lastRoute.params)) {
// this should always be broadcast when params change
$rootScope.$broadcast('$routeUpdate');
}
var current = $route.current;
$route.current = lastRoute;
// make a route change to the previous route work
$timeout(function() {
if (requestId !== currentRequestId) return;
$route.current = current;
});
});
// if it didn't fire for some reason, don't intercept the next one
$timeout(un);
}
return original.call($location, param);
};
});
}]);
Ответ 8
В ответах выше, включая GitHub, были некоторые проблемы для моего сценария, а также кнопка возврата или прямого изменения URL-адреса в браузере - это перезагрузка контроллера, что мне не понравилось. Наконец я пошел со следующим подходом:
1. Определите свойство в определениях маршрутов, называемое noReload, для тех маршрутов, где вы не хотите, чтобы контроллер перезагружался при изменении маршрута.
.when('/:param1/:param2?/:param3?', {
templateUrl: 'home.html',
controller: 'HomeController',
controllerAs: 'vm',
noReload: true
})
2. В функции запуска вашего модуля поместите логику, которая проверяет эти маршруты. Это предотвратит перезагрузку, только если noReload
- true
, а предыдущий маршрут - тот же.
fooRun.$inject = ['$rootScope', '$route', '$routeParams'];
function fooRun($rootScope, $route, $routeParams) {
$rootScope.$on('$routeChangeStart', function (event, nextRoute, lastRoute) {
if (lastRoute && nextRoute.noReload
&& lastRoute.controller === nextRoute.controller) {
var un = $rootScope.$on('$locationChangeSuccess', function () {
un();
// Broadcast routeUpdate if params changed. Also update
// $routeParams accordingly
if (!angular.equals($route.current.params, lastRoute.params)) {
lastRoute.params = nextRoute.params;
angular.copy(lastRoute.params, $routeParams);
$rootScope.$broadcast('$routeUpdate', lastRoute);
}
// Prevent reload of controller by setting current
// route to the previous one.
$route.current = lastRoute;
});
}
});
}
3. Наконец, в контроллере прослушайте событие $routeUpdate, чтобы вы могли делать все, что вам нужно, когда изменяются параметры маршрута.
HomeController.$inject = ['$scope', '$routeParams'];
function HomeController($scope, $routeParams) {
//(...)
$scope.$on("$routeUpdate", function handler(route) {
// Do whatever you need to do with new $routeParams
// You can also access the route from the parameter passed
// to the event
});
//(...)
}
Имейте в виду, что при таком подходе вы не меняете вещи в контроллере, а затем обновляете путь соответствующим образом. Это наоборот. Сначала вы меняете путь, а затем слушаете событие $routeUpdate, чтобы изменить ситуацию в контроллере, когда изменяются параметры маршрута.
Это упрощает и согласовывает вещи, поскольку вы можете использовать ту же логику, когда вы просто меняете путь (но без дорогостоящих $http-запросов, если хотите) и когда вы полностью перезагружаете браузер.
Ответ 9
Добавьте следующий тег заголовка
<script type="text/javascript">
angular.element(document.getElementsByTagName('head')).append(angular.element('<base href="' + window.location.pathname + '" />'));
</script>
Это предотвратит перезагрузку.
Ответ 10
Существует простой способ изменить путь без перезагрузки
URL is - http://localhost:9000/#/edit_draft_inbox/1457
Используйте этот код для изменения URL, страница не будет перенаправлена
Второй параметр "ложь" очень важен.
$location.path('/edit_draft_inbox/'+id, false);
Ответ 11
Начиная с версии 1.2, вы можете использовать $ location.replace():
$location.path('/items');
$location.replace();