你能在不重新加载AngularJS中的控制器的情况下更改路径吗?

以前有人问过这个问题,从答案来看,情况并不乐观。我想问与此示例代码在考虑..

我的应用程序将当前项目加载到提供该项目的服务中。有几个控制器可以在不重新加载项目的情况下操作项目数据。

如果尚未设置,我的控制器将重新加载该项目,否则,它将在控制器之间使用当前从服务加载的项目。

问题:我想在不重新加载item.HTML的情况下为每个控制器使用不同的路径。

1)这可能吗?

2)如果这是不可能的,有没有更好的方法让每个控制器都有一个路径,而不是我在这里想出来的?

应用程序.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'});
}]);

项.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>

控制器.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.

应用程序.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'});
}]);

项.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>

控制器.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];
}

指令.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');
}
});
}
});

我的部分中的内容用_0的_ABC包装。

172487 次浏览

如果您不必使用#/item/\{\{item.id}}/foo#/item/\{\{item.id}}/bar等URL,而是使用#/item/\{\{item.id}}/?foo#/item/\{\{item.id}}/?bar,则可以为/item/\{\{item.id}}/设置路由,将reloadOnSearch设置为falsehttps://docs.angularjs.org/api/ngRoute/provider/$routeProvider)。这告诉AngularJS,如果URL的搜索部分发生更改,则不要重新加载视图。

虽然这篇文章是旧的,并且已经有了一个被接受的答案,但对于我们这些需要改变实际路径而不仅仅是参数的人来说,使用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

如果您需要更改路径,请将其添加到应用程序文件中的.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,它是我找到的最优雅的解决方案。

为什么不把NG控制器提高一级,

<body ng-controller="ProjectController">
<div ng-view><div>

并且不在路由中设置控制器,

.when('/', { templateUrl: "abc.html" })

对我有用。

对于那些需要在不重新加载控制器的情况下更改路径()的人- 以下是插件:https://github.com/anglibs/angular-location-update

用法:

$location.update_path('/notes/1');

基于https://stackoverflow.com/a/24102139/1751321

P.S.此解决方案https://stackoverflow.com/a/24102139/1751321包含错误 在调用

path(,false)之后-它将中断浏览器导航,直到调用path(,true)。

我用这个解决方案

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;
});
});

这种方法保留了后退按钮的功能,并且模板中始终有当前的RouteParams,这与我见过的其他一些方法不同。

下面是我的完整解决方案,它解决了@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);
    };
    });
    }]);
    

上面的答案,包括GitHub的答案,在我的场景中有一些问题,而且后退按钮或浏览器的直接URL更改正在重新加载控制器,这是我不喜欢的。我最终采用了以下方法:

1.在路由定义中定义一个名为“ noreload ”的属性,用于您不希望控制器在路由更改时重新加载的路由。

.when('/:param1/:param2?/:param3?', {
templateUrl: 'home.html',
controller: 'HomeController',
controllerAs: 'vm',
noReload: true
})

_模块的运行功能中的ABC_2,输入检查这些路线的逻辑。仅当noReloadtrue且先前的路由控制器相同时,它才会阻止重新加载。

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请求)和完全重新加载浏览器时使用相同的逻辑。

添加以下内部head标记

  <script type="text/javascript">
angular.element(document.getElementsByTagName('head')).append(angular.element('<base href="' + window.location.pathname + '" />'));
</script>

这将阻止重新加载。

有一种简单的方法可以在不重新加载的情况下更改路径

URL is - http://localhost:9000/#/edit_draft_inbox/1457

使用此代码更改URL,页面将不会被重定向

第二个参数“ false ”非常重要。

$location.path('/edit_draft_inbox/'+id, false);

从版本1.2开始,您可以使用$location.replace()

$location.path('/items');
$location.replace();

我不能让这里的任何答案起作用。作为一个可怕的黑客,当我更改路由时,我在本地存储中存储了一个时间戳,并在页面初始化时检查该时间戳是否已设置并且是最近的,在这种情况下,我不会触发一些初始化操作。

在控制器中:

window.localStorage['routeChangeWithoutReloadTimestamp'] = new Date().getTime();
$location.path(myURL);

在配置中:

.when(myURL, {
templateUrl: 'main.html',
controller:'MainCtrl',
controllerAs: 'vm',
reloadOnSearch: false,
resolve:
{
var routeChangeWithoutReloadTimestamp =
window.localStorage['routeChangeWithoutReloadTimestamp'];
var currentTimestamp = new Date().getTime();
if (!routeChangeWithoutReloadTimestamp ||
currentTimestamp - routeChangeWithoutReloadTimestamp >= 5000) {
//initialization code here
}
//reset the timestamp to trigger initialization when needed
window.localStorage['routeChangeWithoutReloadTimestamp'] = 0;
}
});

我使用了时间戳而不是布尔值,以防代码在有机会重新初始化更改路由之前存储的值之前被中断。标签之间发生碰撞的风险非常低。