97

Our application has 2-level navigating. We want to use AngularJS $routeProvider to dynamically provide templates to an <ng-view />. I was thinking of doing something along the lines of this:

angular.module('myApp', []).
config(['$routeProvider', function($routeProvider) {
    $routeProvider.when('/:primaryNav/:secondaryNav', {
        templateUrl: 'resources/angular/templates/nav/'+<<primaryNavHere>>+'/'+<<secondaryNavHere>>+'.html'
    });
}]);

I just don't know how to populate the parts within the <<>>. I know the primaryNav and secondaryNav get bound to the $routeParams, but how do I access $routeParams here in order to dynamically serve up the template?

I159
  • 29,741
  • 31
  • 97
  • 132
dnc253
  • 39,967
  • 41
  • 141
  • 157

8 Answers8

131

This very helpful feature is now available starting at version 1.1.2 of AngularJS. It's considered unstable but I have used it (1.1.3) and it works fine.

Basically you can use a function to generate a templateUrl string. The function is passed the route parameters that you can use to build and return the templateUrl string.

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

app.config(
    function($routeProvider) {
        $routeProvider.
            when('/', {templateUrl:'/home'}).
            when('/users/:user_id', 
                {   
                    controller:UserView, 
                    templateUrl: function(params){ return '/users/view/' + params.user_id; }
                }
            ).
            otherwise({redirectTo:'/'});
    }
);

Many thanks to https://github.com/lrlopez for the pull request.

https://github.com/angular/angular.js/pull/1524

jlareau
  • 2,860
  • 1
  • 17
  • 11
  • 5
    This is now fully supported in 1.2 and it probably the best way: http://docs.angularjs.org/api/ngRoute/provider/$routeProvider – Stu Feb 25 '14 at 10:26
  • what about a dynamic controller based on the params? – Oak Apr 05 '14 at 02:14
  • Please, please (please!) tell me that this is possible to do with `controllers`...? – Cody May 16 '14 at 22:31
  • how do you access the the controller from the template in this case (supplied by $routeProvider)? Normally, if the controller is bound by ng-controller="myController" directive, you can reference it myController as myCtrl. How do I define myCtrl in this case? – FlavorScape Jun 10 '14 at 22:49
  • @FlavorScape I just had this issue too - the solution is to do something like $routeProvider.when("/foo", { controller : "FooController", controllerAs : "foo", templateUrl: "foo.html" }); – Erin Drummond Sep 24 '14 at 22:36
84

I couldn't find a way to inject and use the $routeParams service (which I would assume would be a better solution) I tried this thinking it might work:

angular.module('myApp', []).
    config(function ($routeProvider, $routeParams) {
        $routeProvider.when('/:primaryNav/:secondaryNav', {
            templateUrl: 'resources/angular/templates/nav/'+$routeParams.primaryNav+'/'+$routeParams.secondaryNav+'.html'
        });
    });

Which yielded this error:

Unknown provider: $routeParams from myApp

If something like that isn't possible you can change your templateUrl to point to a partial HTML file that just has ng-include and then set the URL in your controller using $routeParams like this:

angular.module('myApp', []).
    config(function ($routeProvider) {
        $routeProvider.when('/:primaryNav/:secondaryNav', {
            templateUrl: 'resources/angular/templates/nav/urlRouter.html',
            controller: 'RouteController'
        });
    });

function RouteController($scope, $routeParams) {
        $scope.templateUrl = 'resources/angular/templates/nav/'+$routeParams.primaryNav+'/'+$routeParams.secondaryNav+'.html';
    }

With this as your urlRouter.html

<div ng-include src="templateUrl"></div>
Devid Farinelli
  • 7,514
  • 9
  • 42
  • 73
Gloopy
  • 37,767
  • 15
  • 103
  • 71
  • It would be nice to not have create the one line html file, but I guess it's not too bad. Thanks for the help! – dnc253 Jul 19 '12 at 03:06
  • 7
    The problem why the former does not work is documented https://groups.google.com/forum/?fromgroups=#!topic/angular/qNi5lqm-Ps8. As injection targets to `config()` only providers are passed, not actual service instances such as `$routePrams`. – nre Aug 25 '12 at 17:13
  • 18
    You actually don't have to create the "one-line html file" - just use the "template:" key instead of "templateUrl" and provide it with a string containing the html one-liner ;-) – DominikGuzei Oct 29 '12 at 10:11
  • Using this technique, how could the templates fetched from the server side be cached? i.e. the template determined by $scope.templateUrl – TsenYing Dec 14 '12 at 20:48
  • I think the templates are cached by default at least by the browser and possibly by angular as well http://docs.angularjs.org/api/ng.$templateCache. This may help too: https://groups.google.com/d/topic/angular/b3_sbR5iUIU/discussion – Gloopy Dec 14 '12 at 21:13
  • 1
    Couldn't you just use `template` instead of `templateUrl`? – kaiser Sep 09 '13 at 12:38
  • 1
    This will actually work, but what about applying a controller for each template selected? Using this method will leave you with only one "master" controller: "RouteController" – Yaniv Efraim Dec 31 '13 at 17:23
  • 9
    if you set `template` to a function, you can pass `$routeParams` into that function: `$routeProvider.when('/:pNav/:sNav', { template: fn($routeParams) { return $routeParams.pNav + '/' + $routeParams.sNav + '.html' } });`. However, you can't dynamically define a controller this way :( – Jakob Jingleheimer Jan 05 '14 at 02:18
19

templateUrl can be use as function with returning generated URL. We can manipulate url with passing argument which takes routeParams.

See the example.

.when('/:screenName/list',{
    templateUrl: function(params){
         return params.screenName +'/listUI'
    }
})

Hope this help.

Ravi K.
  • 241
  • 3
  • 3
7

Alright, think I got it...

Little background first: The reason I needed this was to stick Angular on top of Node Express and have Jade process my partials for me.

So here's whatchya gotta do... (drink beer and spend 20+ hours on it first!!!)...

When you set up your module, save the $routeProvider globally:

// app.js:
var routeProvider
    , app = angular.module('Isomorph', ['ngResource']).config(function($routeProvider){

        routeProvider = $routeProvider;
        $routeProvider
            .when('/', {templateUrl: '/login', controller: 'AppCtrl'})
            .when('/home', {templateUrl: '/', controller: 'AppCtrl'})
            .when('/login', {templateUrl: '/login', controller: 'AppCtrl'})
            .when('/SAMPLE', {templateUrl: '/SAMPLE', controller: 'SAMPLECtrl'})
            .when('/map', {templateUrl: '/map', controller: 'MapCtrl'})
            .when('/chat', {templateUrl: '/chat', controller: 'ChatCtrl'})
            .when('/blog', {templateUrl: '/blog', controller: 'BlogCtrl'})
            .when('/files', {templateUrl: '/files', controller: 'FilesCtrl'})
            .when('/tasks', {templateUrl: '/tasks', controller: 'TasksCtrl'})
            .when('/tasks/new', {templateUrl: '/tasks/new', controller: 'NewTaskCtrl'})
            .when('/tasks/:id', {templateUrl: '/tasks', controller: 'ViewTaskCtrl'})
            .when('/tasks/:id/edit', {templateUrl: '/tasks', controller: 'EditTaskCtrl'})
            .when('/tasks/:id/delete', {templateUrl: '/tasks', controller: 'DeleteTaskCtrl'})
        .otherwise({redirectTo: '/login'});

});

// ctrls.js
...
app.controller('EditTaskCtrl', function($scope, $routeParams, $location, $http){

    var idParam = $routeParams.id;
    routeProvider.when('/tasks/:id/edit/', {templateUrl: '/tasks/' + idParam + '/edit'});
    $location.path('/tasks/' + idParam + '/edit/');

});
...

That may be more info than what was needed...

  • Basically, you'll wanna store your Module's $routeProvider var globally, eg as routeProvider so that it can be accessed by your Controllers.

  • Then you can just use routeProvider and create a NEW route (you can't 'RESET a route' / 'REpromise'; you must create a new one), I just added a slash (/) at the end so that it is as semantic as the first.

  • Then (inside your Controller), set the templateUrl to the view you want to hit.

  • Take out the controller property of the .when() object, lest you get an infinite request loop.

  • And finally (still inside the Controller), use $location.path() to redirect to the route that was just created.

If you're interested in how to slap an Angular app onto an Express app, you can fork my repo here: https://github.com/cScarlson/isomorph.

And this method also allows for you to keep the AngularJS Bidirectional Data-Bindings in case you want to bind your HTML to your database using WebSockets: otherwise without this method, your Angular data-bindings will just output {{model.param}}.

If you clone this at this time, you'll need mongoDB on your machine to run it.

Hope this solves this issue!

Cody

Don't drink your bathwater.

Wilfredo P
  • 4,070
  • 28
  • 46
Cody
  • 9,785
  • 4
  • 61
  • 46
3

I've added support for this in my fork of angular. It allows you to specify

$routeProvider
    .when('/:some/:param/:filled/:url', {
          templateUrl:'/:some/:param/:filled/template.ng.html'
     });

https://github.com/jamie-pate/angular.js/commit/dc9be174af2f6e8d55b798209dfb9235f390b934

not sure this will get picked up as it is kind of against the grain for angular, but it is useful to me

d-_-b
  • 21,536
  • 40
  • 150
  • 256
Jamie Pate
  • 1,783
  • 20
  • 18
  • Right on! Havent used it yet but REALLY need it. Are there any other caveats that I should watch out for? Also, any word on Angular implementing it? - I like to use the CDN. – Cody Mar 11 '13 at 11:50
  • No idea, I never got the paperwork signed so this a dead end, but you could take the change and try to get it pulled in yourself (I no longer work at the company I was with when I did this, and they didn't pursue angularjs any further) – Jamie Pate Mar 11 '13 at 19:01
  • Ok, thanks for the info and your contribution to the issue - I'll be pulling that and playing around with it in a minute. – Cody Mar 13 '13 at 15:38
3

Router:-

...
.when('/enquiry/:page', {
    template: '<div ng-include src="templateUrl" onload="onLoad()"></div>',
    controller: 'enquiryCtrl'
})
...

Controller:-

...
// template onload event
$scope.onLoad = function() {
    console.log('onLoad()');
    f_tcalInit();  // or other onload stuff
}

// initialize
$scope.templateUrl = 'ci_index.php/adminctrl/enquiry/'+$routeParams.page;
...

I believe it is a weakness in angularjs that $routeParams is NOT visible inside the router. A tiny enhancement would make a world of difference during implementation.

Ron deBoer
  • 41
  • 4
1
//module dependent on ngRoute  
 var app=angular.module("myApp",['ngRoute']);
    //spa-Route Config file
    app.config(function($routeProvider,$locationProvider){
          $locationProvider.hashPrefix('');
          $routeProvider
            .when('/',{template:'HOME'})
            .when('/about/:paramOne/:paramTwo',{template:'ABOUT',controller:'aboutCtrl'})
            .otherwise({template:'Not Found'});
    }
   //aboutUs controller 
    app.controller('aboutCtrl',function($routeParams){
          $scope.paramOnePrint=$routeParams.paramOne;
          $scope.paramTwoPrint=$routeParams.paramTwo;
    });

in index.html

<a ng-href="#/about/firstParam/secondParam">About</a>

firstParam and secondParam can be anything according to your needs.

Aniket Jha
  • 1,751
  • 11
  • 13
0

I was having a similar issue and used $stateParams instead of routeParam

Devid Farinelli
  • 7,514
  • 9
  • 42
  • 73
mcneela86
  • 1,029
  • 3
  • 18
  • 35