Dynamically creating routes in Angular JS

We are trying to make a switch to angular, but we have a pretty big routing problem. Our current site has something like 10,000 unique routes - each page has a unique ".html" ID. There is no specific convention that would allow us to assign a controller to them, so I created a search API endpoint.

Here's the workflow I'm trying to create:

  • Angular application loading. One route is configured differently.

  • When someone clicks on a link, I don't know if the resource is a product or a category, so the search endpoint request is made with the unique identifier ".html". The endpoint returns two things: the resource name and the identifier (for example, "product" and "10"). So to be clear, they ended up on a page like " http://www.example.com/some-identifier.html ," I am querying the search API to find out what resource it is and get a result like "product" and " 10 "- now I know its product controller / template and I need data from product id 10.

  • The application assigns a controller and template ("productController" and "product.html"), requests the correct endpoint for the data ("/ api / product / 10"), and renders the template.

Problems I am facing:

  • $ http is not available during configuration, so I cannot get into the lookup table.

  • Adding routes after config is sloppy at best - I did it successfully by assigning $ routeProvider to a global variable and doing it after the fact, but man, this is ugly.

  • Loading all routes seems impractical - the file size will be quite heavy for a lot of connections / browsers.

  • We cannot change the agreement now. We have 4 years of SEO and a lot of organic traffic to get rid of our URLs.

It seems to me that I am thinking about this the wrong way and there is something missing. The lookup table is really a problem - not knowing which resource to download (product, category, etc.). I read this article about loading routes dynamically, but again, it doesn't make an external request. For us, loading controllers is not a problem, it resolves routes and then assigns them c

How would you solve the problem?

Decision

Thank you so much @ user2943490 for pointing me in the right direction. Don't forget to boost your answer! I made it a little more general so that I don't need to define route types.

API structure

This configuration requires at least two endpoints: /api/routes/lookup/:resource_to_lookup:/

and /api/some_resource_type/id/:some_resource_id:/

. We request a lookup to find out what resource it points to and what the resource ID is. This allows you to have nice clean URLs like: " http://www.example.com/thriller.html " (one) and " http://www.example.com/michaeljackson.html " (collection).

In my case, if I ask for something like "awesome_sweatshirt.html", my search will return a JSON object with "{type: 'product', id: 10}". Then I ask for "/ api / product / id / 10" to get the data.

"Isn't it that slow?" you ask. With the varnish on the front, it all happens in less than 1 second. We can see that the pageload time locally is less than 20ms. Through the wire from the slow server-to-server was closer to half a second.

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')
    }

}]);

      

+3


source to share


4 answers


Try something like this.

In the route configuration, you configured the definition for each resource type and their controllers, templates, and permissions:

$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

      



Then you listen $locationChangeStart

. If the URL you're navigating to is a "unique identifier", request a search. Depending on the type of resource returned by the search, navigate to the correct route above.

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

      

+2


source


You can use $ routeParams for this.

eg.



route/:type/:id

      

so the type and id can be completely dynamic, different type handling will be up to the route controller.

0


source


What if you have a json file with route information (and if there is no security issue) and iterate over it to attach routes to the application?

eg.

JSON:

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

      

And then iterate over the JSON content and join them to $routeProvider.when

? I'm not sure if this is a good idea, it depends on how large the JSON file is going to be and if you don't want to expose all your routes to a potential attacker.

0


source


From AngularJS documentation ,

The $ routeParams service allows you to get the current set of route parameters.

Dependencies: $route

An example looks like

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

      

0


source







All Articles