在Angular JS中动态创建路由

时间:2014-12-08 21:35:18

标签: javascript angularjs angularjs-routing

我们正在尝试切换到角度,但我们在路由方面存在很大的问题。我们当前的网站有10,000条独特的路线 - 永远的页面有一个独特的“.html”标识符。没有特定的约定允许我们将控制器分配给它们,所以我创建了一个查找API端点。

以下是我正在尝试创建的工作流程:

  • 角度应用加载。设置了一条“其他”路线。

  • 当有人点击链接时,我不知道该资源是产品还是类别,因此使用唯一的“.html”标识符对查找端点进行查询。端点返回两件事:资源名称和ID(例如“product”和“10”)。所以要清楚,他们点击了一个页面,“http://www.example.com/some-identifier.html,”我查询查找API以找出这是什么类型的资源,并得到像“产品”和“10”的结果 - 现在我知道它是产品控制器/模板,我需要产品ID 10的数据。

  • 应用程序分配控制器和模板(“productController”和“product.html”),查询正确的数据端点(“/ api / product / 10”),并呈现模板。

我遇到的问题:

  • $ http在配置期间不可用,因此我无法点击查找表。

  • 在配置完成之后添加路由最好 - 我已经成功地通过将$ routeProvider分配给一个全局变量并在事后做了它,但是伙计,这很难看。

  • 加载所有路由似乎不切实际 - 对于很多连接/浏览器来说,文件的大小就相当大了。

  • 我们现在无法改变惯例。我们有4年的搜索引擎优化和大量的自然流量来放弃我们的网址。

我觉得我可能会以错误的方式思考这个问题并且缺少某些东西。查找表确实是问题 - 不知道要加载什么类型的资源(产品,类别等)。我阅读this article关于动态加载路由,但同样,他没有进行外部查询。对我们来说,加载控制器不是问题,它正在解析路由,然后分配它们c

你会如何解决这个问题?

解决方案

非常感谢@ user2943490让我指向正确的方向。 不要忘记提出他的答案!我使它更加通用,以便我不必定义路线类型。

API结构

此配置至少需要两个端点:/api/routes/lookup/:resource_to_lookup://api/some_resource_type/id/:some_resource_id:/。我们查询查找以找出它指向的资源类型以及资源的ID。这使您可以拥有漂亮干净的网址,例如“http://www.example.com/thriller.html”(单个)和“http://www.example.com/michaeljackson.html”(集合)。

在我的情况下,如果我查询类似“awesome_sweatshirt.html”的内容,我的查找将返回一个带有“{type:'product',id:10}”的JSON对象。然后我查询“/ api / product / id / 10”来获取数据。

“那不是很慢吗?”你问。前面有清漆,所有这些都发生在不到1秒的时间。我们在本地看到的页面加载时间少于20毫秒。来自慢速开发服务器的电线接近半秒钟。

app.js

var app = angular.module('myApp', [
    'ngRoute'
])

.config(function($routeProvider, $locationProvider) {

    $routeProvider
    .otherwise({
        controller: function($scope, $routeParams, $controller, lookupService) {
            /* this creates a child controller which, if served as it is, should accomplish your goal behaving as the actual controller (params.dashboardName + "Controller") */
            if ( typeof lookupService.controller == "undefined" )
                return; 

            $controller(lookupService.controller, {$scope:$scope});
            delete lookupService.controller;
            //We have to delete it so that it doesn't try to load again before the next lookup is complete.
        },        
        template: '<div ng-include="templateUrl"></div>'
    });

    $locationProvider.html5Mode(true);
})

.controller('appController', ['$scope', '$window', '$rootScope', 'lookupService', '$location', '$route', function($scope, $window, $rootScope, lookupService, $location, $route){

    $rootScope.$on('$locationChangeStart', handleUniqueIdentifiers);

    function handleUniqueIdentifiers (event, currentUrl, previousUrl) {
        window.scrollTo(0,0)

        // Only intercept those URLs which are "unique identifiers".
        if (!isUniqueIdentifierUrl($location.path())) {
            return;
        }

        // Show the page load spinner
        $scope.isLoaded = false  

        lookupService.query($location.path())
            .then(function (lookupDefinition) {
                $route.reload();
            })
            .catch(function () {
                // Handle the look up error.
            });
    }

    function isUniqueIdentifierUrl (url) {
        // Is this a unique identifier URL?
        // Right now any url with a '.html' is considered one, substitute this
        // with your actual business logic.
        return url.indexOf('.html') > -1;
    }
}]);

lookupService.js

myApp.factory('lookupService', ['$http', '$q', '$location', function lookupService($http, $q, $location) {
    return {
        id: null,
        originalPath: '',
        contoller: '',
        templateUrl: '',
        query: function (url) {
            var deferred = $q.defer();
            var self = this;

            $http.get("/api/routes/lookup"+url)
            .success(function(data, status, headers, config){
                self.id = data.id;
                self.originalPath = url;
                self.controller = data.controller+'Controller';
                self.templateUrl = '/js/angular/components/'+data.controller+'/'+data.controller+'.html';
                //Our naming convention works as "components/product/product.html" for templates
                deferred.resolve(data);
            })

            return deferred.promise;
        }
    }
}]);

productController.js

myApp.controller('productController', ['$scope', 'productService', 'cartService', '$location', 'lookupService', function ($scope, productService, cartService, $location, lookupService) {

    $scope.cart = cartService

    // ** This is important! ** //
    $scope.templateUrl = lookupService.templateUrl

    productService.getProduct(lookupService.id).then(function(data){
        $scope.data = data
        $scope.data.selectedItem = {}
        $scope.$emit('viewLoaded')
    });

    $scope.addToCart = function(item) {
        $scope.cart.addProduct(angular.copy(item))
        $scope.$emit('toggleCart')
    }

}]);

4 个答案:

答案 0 :(得分:2)

尝试这样的事情。

在路由配置中,您可以为每种资源类型及其控制器,模板和解析设置定义:

$routeProvider.when('/products', {
    controller: 'productController',
    templateUrl: 'product.html',
    resolve: {
        product: function ($route, productService) {
            var productId = $route.current.params.id;
            // productService makes a request to //api/product/<productId>
            return productService.getProduct(productId);
        }
    }
});
// $routeProvider.when(...
// add route definitions for your other resource types

然后你会听$locationChangeStart。如果导航到的URL是“唯一标识符”,则查询查找。根据查找返回的资源类型,导航到上面定义的正确路由。

$rootScope.$on('$locationChangeStart', handleUniqueIdentifiers);

function handleUniqueIdentifiers (event, currentUrl, previousUrl) {
    // Only intercept those URLs which are "unique identifiers".
    if (!isUniqueIdentifierUrl(currentUrl)) {
        return;
    }

    // Stop the default navigation.
    // Now you are in control of where to navigate to.
    event.preventDefault();

    lookupService.query(currentUrl)
        .then(function (lookupDefinition) {
            switch (lookupDefinition.type) {
                case 'product':
                    $location.url('/products');
                    break;
                case 'category':
                    $location.url('/categories');
                    break;
                // case ...
                // add other resource types
            }
            $location.search({
                // Set the resource's ID in the query string, so
                // it can be retrieved by the route resolver.
                id: lookupDefinition.id
            });
        })
        .catch(function () {
            // Handle the look up error.
        });
}

function isUniqueIdentifierUrl (url) {
    // Is this a unique identifier URL?
    // Right now any url with a '.html' is considered one, substitute this
    // with your actual business logic.
    return url.indexOf('.html') > -1;
}

答案 1 :(得分:0)

您可以使用$ routeParams

e.g。

route/:type/:id

所以type和id可以是完全动态的,不同的类型处理将由路由的控制器决定。

答案 2 :(得分:0)

如果你有一个带有路由信息的json文件(如果没有安全问题)并迭代它以将路由附加到应用程序怎么办?

e.g。

JSON:

routes: [
{ 
  controller: "Controller1"
  path: "/path1"
  templateUrl: 'partials/home/home.html'
},
{ 
  controller: "Controller1"
  path: "/path1"
  templateUrl: 'partials/home/home.html'
}   
]

然后迭代JSON的内容并将它们附加到$routeProvider.when? 我不确定这是不是一个好主意,取决于JSON文件的大小,以及你是否不希望将所有路由暴露给可能的攻击者。

答案 3 :(得分:0)

来自the AngularJS documentation

  

$ routeParams服务允许您检索当前的一组   路线参数。

     

依赖关系:$route

示例看起来像

// Given:
// URL: http://server.com/index.html#/Chapter/1/Section/2?search=moby
// Route: /Chapter/:chapterId/Section/:sectionId
// Then
$routeParams ==> {chapterId:'1', sectionId:'2', search:'moby'}

ngRouteModule.provider('$routeParams', $RouteParamsProvider);

function $RouteParamsProvider() {
  this.$get = function() { return {}; };
}