AngularJS 动态路由
Posted
技术标签:
【中文标题】AngularJS 动态路由【英文标题】:AngularJS dynamic routing 【发布时间】:2012-11-20 19:01:39 【问题描述】:我目前有一个内置路由的 AngularJS 应用程序。 它工作正常,一切正常。
我的 app.js 文件如下所示:
angular.module('myapp', ['myapp.filters', 'myapp.services', 'myapp.directives']).
config(['$routeProvider', function ($routeProvider)
$routeProvider.when('/', templateUrl: '/pages/home.html', controller: HomeController );
$routeProvider.when('/about', templateUrl: '/pages/about.html', controller: AboutController );
$routeProvider.when('/privacy', templateUrl: '/pages/privacy.html', controller: AboutController );
$routeProvider.when('/terms', templateUrl: '/pages/terms.html', controller: AboutController );
$routeProvider.otherwise( redirectTo: '/' );
]);
我的应用内置了一个 CMS,您可以在其中复制和添加新的 html 文件到 /pages 目录中。
即使对于新的动态添加的文件,我仍然希望通过路由提供程序。
在理想情况下,路由模式应该是:
$routeProvider.when('/pagename', templateUrl: '/pages/pagename.html', controller: CMSController );
因此,如果我的新页面名称是“contact.html”,我希望 Angular 选择“/contact”并重定向到“/pages/contact.html”。
这有可能吗?!如果是的话怎么办?!
更新
现在我的路由配置中有这个:
$routeProvider.when('/page/:name', templateUrl: '/pages/home.html', controller: CMSController )
在我的 CMSController 中:
function CMSController($scope, $route, $routeParams)
$route.current.templateUrl = '/pages/' + $routeParams.name + ".html";
alert($route.current.templateUrl);
CMSController.$inject = ['$scope', '$route', '$routeParams'];
这会将当前的 templateUrl 设置为正确的值。
然而我现在想用新的 templateUrl 值更改 ng-view。这是如何实现的?
【问题讨论】:
【参考方案1】:angular.module('myapp', ['myapp.filters', 'myapp.services', 'myapp.directives']).
config(['$routeProvider', function($routeProvider)
$routeProvider.when('/page/:name*',
templateUrl: function(urlattr)
return '/pages/' + urlattr.name + '.html';
,
controller: 'CMSController'
);
]);
添加 * 让您可以动态处理多级目录。
示例:/page/cars/selling/list 将被此提供商捕获
来自文档(1.3.0):
"如果templateUrl是一个函数,它会被调用如下 参数:
Array. - 从当前提取的路由参数 $location.path() 通过应用当前路由"
还有
何时(路径,路线):方法
路径可以包含以冒号开头并以星号结尾的命名组:例如:名称*。当路由匹配时,所有字符都急切地存储在给定名称下的 $routeParams 中。
【讨论】:
需要与时俱进...我构建的 v1.0.2 中缺少的功能现在可用。更新已接受的答案 老实说,我从来没有在当前的 1.3 测试版中使用过这个 当我这样做时,它实际上已经工作了...... when('/:page', templateUrl : function(parameters) return parameters.page + '.html'; 说真的.. Angular 没有将它作为默认行为真的很愚蠢......手动路由很荒谬 请解释一下,urlattr
是从哪里设置或发送的,我的意思是urlattr.name
会产生什么?【参考方案2】:
好的,解决了。
将解决方案添加到 GitHub - http://gregorypratt.github.com/AngularDynamicRouting
在我的 app.js 路由配置中:
$routeProvider.when('/pages/:name',
templateUrl: '/pages/home.html',
controller: CMSController
);
然后在我的 CMS 控制器中:
function CMSController($scope, $route, $routeParams)
$route.current.templateUrl = '/pages/' + $routeParams.name + ".html";
$.get($route.current.templateUrl, function (data)
$scope.$apply(function ()
$('#views').html($compile(data)($scope));
);
);
...
CMSController.$inject = ['$scope', '$route', '$routeParams'];
#views 是我的<div id="views" ng-view></div>
所以现在它适用于标准路由和动态路由。
为了测试它,我复制了 about.html 并将其命名为portfolio.html,更改了其中的一些内容并在我的浏览器中输入了/#/pages/portfolio
,然后显示了嘿presto portfolio.html....
更新 在 html 中添加了 $apply 和 $compile,以便可以注入动态内容。
【讨论】:
这仅适用于静态内容,如果我理解正确的话。这是因为您在通过 jQuery(在 Angular 范围之外)实例化控制器后正在更改 DOM。像 this 这样的变量可能会起作用(我必须尝试一下),但指令很可能不会。请注意这一点,因为它现在可能很有用,但以后可能会破坏代码.. 没错,绑定不起作用,但对我来说,我并不太担心,因为我只希望客户能够添加新页面。我添加的任何页面我都会通过路由配置适当地指定。不过好点。 如果要渲染静态 html 内容,这不是一个糟糕的解决方案。只要您牢记,您本质上就是用静态(非角度)内容覆盖 ng-view。考虑到这一点,将两者分开会更干净,创建类似 元素,并在那里注入您的“用户模板”。此外,重写 $route.current.templateUrl 绝对没有意义 - 创建一个 $scope.userTemplate 模型并执行 $('#user-views").load($scope.userTemplate) 更干净,并且不会破坏任何角度代码。 不 - ng-view 指令相当有限 - 或者更好地说,它专门用于本机路由系统(反过来,这仍然是有限的,恕我直言)。你可以照你说的做,将内容注入到 DOM 元素中,然后调用 $compile 方法,告诉 angular 重新读取结构。这将触发任何需要完成的绑定。 有效,但您不应该在控制器中进行 DOM 操作。【参考方案3】:我认为最简单的方法是稍后解析路由,例如,您可以通过 json 查询路由。看看我在配置阶段通过 $provide 从 $routeProvider 中创建了一个工厂,所以我可以在运行阶段甚至在控制器中继续使用 $routeProvider 对象。
'use strict';
angular.module('myapp', []).config(function($provide, $routeProvider)
$provide.factory('$routeProvider', function ()
return $routeProvider;
);
).run(function($routeProvider, $http)
$routeProvider.when('/',
templateUrl: 'views/main.html',
controller: 'MainCtrl'
).otherwise(
redirectTo: '/'
);
$http.get('/dynamic-routes.json').success(function(data)
$routeProvider.when('/',
templateUrl: 'views/main.html',
controller: 'MainCtrl'
);
// you might need to call $route.reload() if the route changed
$route.reload();
);
);
【讨论】:
别名$routeProvider
用作factory
(在config
之后可用)不会很好地发挥安全性和应用程序凝聚力——无论哪种方式,很酷的技术(赞成!)。
@Cody 你能解释一下安全/应用凝聚力说明吗?我们在一条类似的船上,类似上面的答案会非常方便。
@virtualandy,这通常不是一个好方法,因为您要让提供程序在不同阶段可用——这反过来可能会泄漏应该在配置阶段隐藏的高级实用程序。我的建议是使用 UI-Router(解决了很多困难),使用“resolve”、“controllerAs”和其他功能,例如将 templateUrl 设置为函数。我还构建了一个“presolve”模块,我可以共享它可以 $interpolate 路由方案的任何部分(模板、templateUrl、控制器等)。上面的解决方案是一个更优雅的解决方案——但您主要关心的是什么?
@virtualandy,这是一个用于lazyLoaded 模板、控制器等的模块——很抱歉它不在回购中,但这里有一个小提琴:jsfiddle.net/cCarlson/zdm6gpnb ... 这个lazyLoads 上面的内容——和—— - 可以根据 URL 参数插入任何内容。该模块可以使用一些工作,但它至少是一个起点。
@Cody - 没问题,感谢您的示例和进一步的解释。有道理。在我们的例子中,我们使用angular-route-segment,它运行良好。但是我们现在需要支持“动态”路由(某些部署可能没有页面的完整功能/路由,或者可能更改它们的名称等),并且在实际实现之前加载一些定义路由的 JSON 的概念是我们的想法但显然在 .config() 中使用 $http/$providers 时遇到了问题。【参考方案4】:
在 $routeProvider URI 模式中,您可以指定可变参数,例如:$routeProvider.when('/page/:pageNumber' ...
,并通过 $routeParams 在控制器中访问它。
$route 页面末尾有一个很好的例子:http://docs.angularjs.org/api/ng.$route
EDIT(针对已编辑的问题):
不幸的是,路由系统非常有限 - 关于这个主题有很多讨论,并且已经提出了一些解决方案,即通过创建多个命名视图等。但是现在,ngView 指令只为每个视图提供一个视图路线,一对一。您可以通过多种方式来解决这个问题 - 更简单的一种是将视图的模板用作加载器,其中包含 <ng-include src="myTemplateUrl"></ng-include>
标记($scope.myTemplateUrl 将在控制器中创建)。
我使用更复杂(但更简洁,用于更大更复杂的问题)的解决方案,基本上完全跳过了 $route 服务,这里详细说明:
http://www.bennadel.com/blog/2420-Mapping-AngularJS-Routes-Onto-URL-Parameters-And-Client-Side-Events.htm
【讨论】:
我喜欢ng-include
方法。它真的很简单,而且这是比操作 DOM 更好的方法。【参考方案5】:
不确定为什么会这样,但动态(或通配符,如果您愿意)路由在角度 1.2.0-rc.2 中是可能的...
http://code.angularjs.org/1.2.0-rc.2/angular.min.js
http://code.angularjs.org/1.2.0-rc.2/angular-route.min.js
angular.module('yadda', [
'ngRoute'
]).
config(function ($routeProvider, $locationProvider)
$routeProvider.
when('/:a',
template: '<div ng-include="templateUrl">Loading...</div>',
controller: 'DynamicController'
).
controller('DynamicController', function ($scope, $routeParams)
console.log($routeParams);
$scope.templateUrl = 'partials/' + $routeParams.a;
).
example.com/foo -> 加载“foo”部分
example.com/bar-> 加载“bar”部分
ng-view 不需要做任何调整。 '/:a' 案例是我发现的唯一可以实现这一点的变量。'/:foo' 不起作用,除非你的部分都是 foo1、foo2 等......'/:a' 适用于任何部分名字。
所有值都会触发动态控制器 - 因此没有“否则”,但我认为这是您在动态或通配符路由场景中寻找的内容..
【讨论】:
【参考方案6】:从 AngularJS 1.1.3 开始,您现在可以使用新的 catch-all 参数完全按照您的意愿行事。
https://github.com/angular/angular.js/commit/7eafbb98c64c0dc079d7d3ec589f1270b7f6fea5
来自提交:
这允许 routeProvider 接受匹配的参数 子字符串,即使它们包含斜杠(如果它们是前缀) 用星号代替冒号。 例如,
edit/color/:color/largecode/*largecode
之类的路由 会匹配这样的东西http://appdomain.com/edit/color/brown/largecode/code/with/slashs
.
我自己测试过(使用 1.1.5),效果很好。请记住,每个新 URL 都会重新加载您的控制器,因此要保持任何类型的状态,您可能需要使用自定义服务。
【讨论】:
【参考方案7】:这是另一种效果很好的解决方案。
(function()
'use strict';
angular.module('cms').config(route);
route.$inject = ['$routeProvider'];
function route($routeProvider)
$routeProvider
.when('/:section',
templateUrl: buildPath
)
.when('/:section/:page',
templateUrl: buildPath
)
.when('/:section/:page/:task',
templateUrl: buildPath
);
function buildPath(path)
var layout = 'layout';
angular.forEach(path, function(value)
value = value.charAt(0).toUpperCase() + value.substring(1);
layout += value;
);
layout += '.tpl';
return 'client/app/layouts/' + layout;
)();
【讨论】:
以上是关于AngularJS 动态路由的主要内容,如果未能解决你的问题,请参考以下文章