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 动态路由的主要内容,如果未能解决你的问题,请参考以下文章

AngularJS 基于路由动态加载控制器和模板

AngularJS - 基于路由动态加载外部 JS

Angularjs动态路由模板Url变化

AngularJS 路由精分

angularJs-route路由详解

angularJs-route路由详解