[javascript] AngularJS dynamic routing

I currently have an AngularJS application with routing built in. It works and everything is ok.

My app.js file looks like this:

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: '/' });
  }]);

My app has a CMS built in where you can copy and add new html files within the /pages directory.

I would like to still go through the routing provider though even for the new dynamically added files.

In an ideal world the routing pattern would be:

$routeProvider.when('/pagename', { templateUrl: '/pages/pagename.html', controller: CMSController });

So if my new page name was "contact.html" I would like angular to pick up "/contact" and redirect to "/pages/contact.html".

Is this even possible?! and if so how?!

Update

I now have this in my routing config:

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

and in my CMSController:

function CMSController($scope, $route, $routeParams) {
    $route.current.templateUrl = '/pages/' + $routeParams.name + ".html";
    alert($route.current.templateUrl);
}
CMSController.$inject = ['$scope', '$route', '$routeParams'];

This sets the current templateUrl to the right value.

However I would now like to change the ng-view with the new templateUrl value. How is this accomplished?

The answer is


I think the easiest way to do such thing is to resolve the routes later, you could ask the routes via json, for example. Check out that I make a factory out of the $routeProvider during config phase, via $provide, so I can keep using the $routeProvider object in the run phase, and even in controllers.

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

Ok solved it.

Added the solution to GitHub - http://gregorypratt.github.com/AngularDynamicRouting

In my app.js routing config:

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

Then in my CMS controller:

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'];

With #views being my <div id="views" ng-view></div>

So now it works with standard routing and dynamic routing.

To test it I copied about.html called it portfolio.html, changed some of it's contents and entered /#/pages/portfolio into my browser and hey presto portfolio.html was displayed....

Updated Added $apply and $compile to the html so that dynamic content can be injected.


Here is another solution that works good.

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

    }

})();

As of AngularJS 1.1.3, you can now do exactly what you want using the new catch-all parameter.

https://github.com/angular/angular.js/commit/7eafbb98c64c0dc079d7d3ec589f1270b7f6fea5

From the commit:

This allows routeProvider to accept parameters that matches substrings even when they contain slashes if they are prefixed with an asterisk instead of a colon. For example, routes like edit/color/:color/largecode/*largecode will match with something like this http://appdomain.com/edit/color/brown/largecode/code/with/slashs.

I have tested it out myself (using 1.1.5) and it works great. Just keep in mind that each new URL will reload your controller, so to keep any kind of state, you may need to use a custom service.


In the $routeProvider URI patters, you can specify variable parameters, like so: $routeProvider.when('/page/:pageNumber' ... , and access it in your controller via $routeParams.

There is a good example at the end of the $route page: http://docs.angularjs.org/api/ng.$route

EDIT (for the edited question):

The routing system is unfortunately very limited - there is a lot of discussion on this topic, and some solutions have been proposed, namely via creating multiple named views, etc.. But right now, the ngView directive serves only ONE view per route, on a one-to-one basis. You can go about this in multiple ways - the simpler one would be to use the view's template as a loader, with a <ng-include src="myTemplateUrl"></ng-include> tag in it ($scope.myTemplateUrl would be created in the controller).

I use a more complex (but cleaner, for larger and more complicated problems) solution, basically skipping the $route service altogether, that is detailed here:

http://www.bennadel.com/blog/2420-Mapping-AngularJS-Routes-Onto-URL-Parameters-And-Client-Side-Events.htm


Not sure why this works but dynamic (or wildcard if you prefer) routes are possible in 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 -> loads "foo" partial

example.com/bar-> loads "bar" partial

No need for any adjustments in the ng-view. The '/:a' case is the only variable I have found that will acheive this.. '/:foo' does not work unless your partials are all foo1, foo2, etc... '/:a' works with any partial name.

All values fire the dynamic controller - so there is no "otherwise" but, I think it is what you're looking for in a dynamic or wildcard routing scenario..


Examples related to javascript

need to add a class to an element How to make a variable accessible outside a function? Hide Signs that Meteor.js was Used How to create a showdown.js markdown extension Please help me convert this script to a simple image slider Highlight Anchor Links when user manually scrolls? Summing radio input values How to execute an action before close metro app WinJS javascript, for loop defines a dynamic variable name Getting all files in directory with ajax

Examples related to angularjs

AngularJs directive not updating another directive's scope ERROR in Cannot find module 'node-sass' CORS: credentials mode is 'include' CORS error :Request header field Authorization is not allowed by Access-Control-Allow-Headers in preflight response WebSocket connection failed: Error during WebSocket handshake: Unexpected response code: 400 Print Html template in Angular 2 (ng-print in Angular 2) $http.get(...).success is not a function Angular 1.6.0: "Possibly unhandled rejection" error Find object by its property in array of objects with AngularJS way Error: Cannot invoke an expression whose type lacks a call signature

Examples related to content-management-system

How organize uploaded media in WP? AngularJS dynamic routing How to repair a serialized string which has been corrupted by an incorrect byte count length? How to get the full URL of a Drupal page?

Examples related to url-routing

Get current url in Angular Vue.js redirection to another page AngularJS dynamic routing AngularJS - Animate ng-view transitions Using routes in Express-js RegEx to match stuff between parentheses Routing HTTP Error 404.0 0x80070002 Getting full URL of action in ASP.NET MVC