AngularJS动态路由

88

我目前拥有一个带路由的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('/页面名称', { templateUrl: '/pages/页面名称.html', controller: CMSController });

因此,如果我的新页面名称是“contact.html”,我希望Angular能够选择“/contact”并重定向到“/pages/contact.html”。

这是否可能?如果可能的话,怎么做呢?!

更新

现在我在我的路由配置中加入了以下内容:

$routeProvider.when('/page/:name', { templateUrl: '/pages/home.html', controller: CMSController })

而在我的CMS控制器中:

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。如何实现这一点?

7个回答

133
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()提取的路由参数”

还有

  

when(path,route):方法

     
      
  • path可以包含以冒号开头并以星号结尾的命名组:例如:name*。当路由匹配时,所有字符都将被急切地存储在给定名称下的$ routeParams中。
  •   

5
需要跟上时代的步伐...我构建的功能在v1.0.2中缺失,现在已经可用。将被接受的答案更新为此答案。 - Greg
说真的,Angular 默认没有这个行为真的很愚蠢……手动路由太荒谬了。 - Guilherme Ferreira
3
请解释一下,urlattr是从哪里设置或发送的,我的意思是urlattr.name会产生什么结果?(注意:这是尽可能简洁地翻译,没有其他额外内容。) - Sami
这个解决方案非常适合捕获子目录的路由,并且应该准确指定子目录的名称。这将捕获从 / 开始的所有路由:$routeProvider.when('/:any*', { templateUrl: function(params){ return params.any; }, controller: 'theController' }); - Yassine Khachlek
很棒的解决方案,它给了我们很多灵活性! - Rajiv
显示剩余3条评论

37

好的,问题已解决。

将解决方案添加到 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'];

在我的代码中,使用了 <div id="views" ng-view></div> 来表示 #views。

现在它支持标准路由和动态路由。

为了测试它,我复制了 about.html 并将其重命名为 portfolio.html,改变了一些内容,并在浏览器中输入了 /#/pages/portfolio,嘿哎呀,portfolio.html 就被显示出来了...

更新 将 $apply 和 $compile 添加到 html 中,以便可以注入动态内容。


2
这仅适用于静态内容,如果我理解正确的话。这是因为您是在实例化控制器之后,通过 jQuery(在 Angular 的作用域之外)修改 DOM。像 {{this}} 这样的变量可能有效(我必须尝试一下),但指令很可能不起作用。要小心,因为它现在可能有用,但以后可能会破坏代码。 - Tiago Roldão
真的,绑定不起作用,但对我来说并不太重要,因为我只希望客户能够添加新页面。我添加的任何页面都会通过路由配置适当指定。不过这是个好观点。 - Greg
1
如果您想呈现静态HTML内容,这并不是一个坏的解决方案。只要记住,您实际上是用静态(非angular)内容覆盖了ng-view。考虑到这一点,最好将两者分开,创建类似于<div id="user-views"></div>元素,并在那里注入您的“用户模板”。此外,完全没有必要覆盖$route.current.templateUrl - 创建一个$scope.userTemplate模型并执行$('#user-views").load($scope.userTemplate)更加清晰,也不会破坏任何angular代码。 - Tiago Roldão
1
不,ng-view指令相当有限 - 或者更准确地说,它是专门用于本机路由系统的(反过来,这仍然是有限的,在我看来)。您可以像您所说的那样将内容注入DOM元素中,然后调用$compile方法,告诉Angular重新读取结构。这将触发任何需要完成的绑定。 - Tiago Roldão
2
可以运行,但是你不应该在控制器中进行DOM操作。 - dmackerman
显示剩余3条评论

16

我认为最简单的方法是稍后解析路由,你可以通过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
@Cody,你能解释一下安全性/应用程序内聚性的注意事项吗?我们处于类似的情况,像上面的答案那样的东西会非常方便。 - virtualandy
2
@virtualandy,这通常不是一个好的方法,因为您正在跨不同阶段提供提供程序--这反过来可能会泄漏应该在配置阶段隐藏的高级实用程序。我的建议是使用UI-Router(处理许多困难),使用“resolve”、“controllerAs”和其他类似于将templateUrl设置为函数的工具。我还构建了一个“presolve”模块,可以共享,可以$interpolate路由方案的任何部分(模板、templateUrl、控制器等)。上述解决方案更加优雅--但是您的主要关注点是什么? - Cody
1
@virtualandy,这里有一个懒加载模块,可以用于模板、控制器等——抱歉它不在仓库中,但这是一个演示:http://jsfiddle.net/cCarlson/zdm6gpnb/ ... 这个模块可以根据URL参数插值任何内容。该模块还需要改进,但至少是一个起点。 - Cody
@Cody - 没问题,感谢提供示例和进一步解释。很有道理。 在我们的情况下,我们正在使用angular-route-segment,它运行得很好。但是我们现在需要支持“动态”路由(某些部署可能没有页面的全部功能/路由,或者可能更改其名称等),并且在实际实现之前加载定义路由的一些JSON是我们的想法,但显然在.config()中使用$http/$providers时遇到了问题。 - virtualandy

7

在$routeProvider的URI模式中,您可以指定变量参数,如下所示: $routeProvider.when('/page/:pageNumber' ... ,并通过$routeParams在控制器中访问它。

在$route页面的末尾有一个很好的例子:http://docs.angularjs.org/api/ng.$route

编辑(针对编辑后的问题):

路由系统非常有限-关于此话题有很多讨论,并提出了一些解决方案,即通过创建多个命名视图等。 但是现在,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 更好的方法。 - Neel

5

不确定为什么会有效果,但在angular 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' 可以与任何部分名称配合使用。

所有值都会触发动态控制器。因此没有“otherwise”,但我认为它是您在动态或通配符路由方案中寻找的东西。


2
从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都会重新加载您的控制器,因此要保留任何状态,您可能需要使用自定义服务。

0

这里有另一个很好的解决方案。

(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;

    }

})();

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接