在AngularJS中动态创建路由

3
我们试图转换到Angular,但是我们在路由方面遇到了一个很大的问题。我们当前的网站有大约10,000个唯一的路由 - 每个页面都有一个唯一的“.html”标识符。没有特定的约定可以让我们将控制器分配给它们,因此我创建了一个查找API端点。
以下是我正在尝试创建的工作流程:
  • Angular应用程序加载。设置了一个“otherwise”路由。

  • 当有人点击链接时,我不知道资源是产品还是类别,因此会使用唯一的“.html”标识符向查找端点发出查询。该端点返回两个内容:资源名称和ID(例如“product”和“10”)。因此,为了明确起见,他们打开了类似“http://www.example.com/some-identifier.html”的页面,我查询查找API以找出这是哪种类型的资源,并获得像“product”和“10”的结果--现在我知道它是产品控制器/模板,我需要从产品ID 10获取数据。

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

我面临的问题:

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

  • 在配置之后添加路由最好是松散的 -- 我曾经通过将$routeProvider分配给全局变量并在事后执行它来成功实现,但是,这很丑陋。

  • 加载所有路由似乎不切实际 -- 对于许多连接/浏览器,文件大小会相当大。

  • 我们现在不能改变惯例。我们有4年的SEO和大量的有机流量来放弃我们的URL。

我觉得我可能想错了,还有一些问题没有解决。查找表确实是问题所在 -- 不知道要加载什么样的资源(产品、类别等)。我阅读了this article关于动态加载路由,但是他也没有进行外部查询。对于我们来说,加载控制器不是问题,问题在于解析路由然后将它们分配给c

你会如何解决这个问题?

解决方案

非常感谢@user2943490为我指明了正确的方向。别忘了给他点赞! 我将其更加通用化,以便不必定义路由类型。 API结构

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

在我的情况下,如果我查询类似"awesome_sweatshirt.html"这样的东西,我的查询将返回一个JSON对象,其中"{type:'product',id:10}"。然后我查询"/api/product/id/10"以获取数据。

“这不会很慢吗?”你问道。有了Varnish,所有这些操作都在不到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')
    }

}]);

你有多少种类型的资源?每种类型都有一个控制器吗?例如,产品有一个productController,类别有一个categoryController等等... - user2943490
@user2943490 没错 - 我会说我们大约有10种资源类型,但产品和类别是其中最重要的。 - Seamus James
4个回答

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

哦,我只能点赞一次。非常感谢。我们将它变得更加通用,效果很棒。我在问题中添加了完整的、更为广泛的解决方案,以帮助那些可能处于类似境地的其他人。再次感谢。 - Seamus James

0

你可以使用 $routeParams 实现这个功能。

例如:

route/:type/:id

因此,类型和ID可以完全动态化,不同类型的处理将由路由控制器负责。


这并不是真正的问题。我们的URL更像是“http://www.example.com/some-identifier.html”,我们不知道那是产品页面还是类别。这就是我创建查找服务的原因 - 我用“some-identifier.html”查询API,它返回“product”和“10”,这样我就知道我需要产品控制器和产品ID 10的数据。 - Seamus James

0
如果您有一个包含路由信息的JSON文件(且不存在安全问题),那么可以遍历该文件并将路由附加到应用程序中。例如:
JSON:
routes: [
{ 
  controller: "Controller1"
  path: "/path1"
  templateUrl: 'partials/home/home.html'
},
{ 
  controller: "Controller1"
  path: "/path1"
  templateUrl: 'partials/home/home.html'
}   
]

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

暴露路由不是问题,JSON文件是一个解决方案,但文件会非常大,初始页面加载速度也会很慢。这就是为什么我创建了查找端点 - 只是为了获取我们需要的那个。 - Seamus James
@SeamusJames 我认为你的端点解决方案是在这种情况下最好的选择,因为你不能改变路由,以避免影响SEO。 - Avraam Mavridis
问题在于我无法让它工作--一旦我找到了资源,我就无法在页面加载后动态创建路由。这就是问题所在。 - Seamus James
@SeamusJames 也许你可以从你的端点获取路径,然后使用 window.location = "http://www.yoururl.com/thepath"; 而不是 Angular 的路由。但这会让事情变得更加混乱。 - Avraam Mavridis

0

来自AngularJS文档

$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 {}; };
}

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