Dynamic loading of controllers with RequireJS

9,916 views
Skip to first unread message

arte.s...@gmail.com

unread,
May 18, 2012, 1:36:37 PM5/18/12
to ang...@googlegroups.com
I'm trying to loading controller dynamically with requirejs. This is my code (the structure of the project is to Angular-seed) :


app.js
-----------------------------------------------------------------
'use strict';

angular.module('myApp', ['myApp.controllers','myApp.filters', 'myApp.services', 'myApp.directives']).
  config(['$routeProvider', function($routeProvider) {
    $routeProvider.when('/view1', {template: 'partials/partial1.html', controller: AppController});
    $routeProvider.when('/view2', {template: 'partials/partial2.html', controller: MyCtrl2});
    $routeProvider.when('/view3', {template: 'partials/partial3.html', controllerName: 'Controller3', external: true});
    $routeProvider.otherwise({redirectTo: '/view1'});
  }]);

function AppController($scope,$rootScope) {
    $rootScope.$on('$beforeRouteChange', function(scope, newRoute){
        var route = newRoute.$route;
        if (route.external) 
            require([route.controllerName], function(c) { 
                angular.module('myApp.controllers', []).controller(route.controllerName, c);
             });
    });
}



partial3.html
-----------------------------------------------------------------
<div ng-controller='Controller3'>
    <p>This is the partial for view 3.</p>
        <p>
          {{ test }}
        </p>
</div>



controller3.html
-----------------------------------------------------------------

define([], function() {
  
  'use strict';

  return ['$scope', function Controller3 ($scope) {
      
      $scope.test = 'OK';

    }];
});


The controller is loaded (before the template, at least so it seems) but the error still having is the following: 

Error: Argument 'Controller3' is not a function, got undefined  

Can someone help me? I'm going crazy

Thanks 

Roberto

Geraldo Lopes de Souza

unread,
May 18, 2012, 7:34:13 PM5/18/12
to AngularJS
Arte,

You have two options imho:
a) Pass 'Controller3' as a Function and not as a string and make it
available like AppController
b) Use the DI goodness of angular and instantiate the function through
its mechanism:
You need to:

define([], function() {
angular.module('myApp').controller('Controller3',
['$scope', function Controller3 ($scope) {
      $scope.test = 'OK';
    }];
);
end;


angular.module('myApp') locates the controller.
controller is a helper function to register a controller by a name.

This way when angular needs it'll be able to ask DI mechanism by the
name.

As discussed previously in this forum, requirejs and DI mechanism can
coexist.
Each one exists serves a different purpose.
With requirejs you can modularize, reach the code and minificate.
DI lets you make the pieces more pluggable, testable.

In fact I like option b) more. Problem is that I was not able to make
it work sucessfully.
I'm experiencing the following problem:

I have a main.js like this:

define(["jquery",
"angular",
"conf",
"controllers/controllers",
"controllers/UsuariosCtrl",
"jquery.alpha",
"jquery.beta",
"filters/filters",
"directives/directives",
"services/services"
], function($, angular, conf, ctrl, UsuariosCtrl) {
$(function() {
$('body').alpha().beta();
// Declare app level module which depends on filters, and
services
var mod = window.angular.module(conf.moduloRaiz, [conf.services,
conf.filters, conf.directives, conf.controllers]).
config(['$routeProvider', function($routeProvider) {
$routeProvider.when('/view1', {template: 'partials/
partial1.html', controller: 'MyCtrl1'});
$routeProvider.when('/view2', {template: 'partials/
partial2.html', controller: 'MyCtrl2'});
$routeProvider.when('/usuarios', {template: 'partials/
usuarios.html', controller: UsuariosCtrl});
$routeProvider.otherwise({redirectTo: '/'});
}]);

angular.bootstrap(window.document,[conf.moduloRaiz,
conf.services, conf.filters, conf.directives, conf.controllers]);
});
});

a controllers/controllers.js like this:

define(["angular", "conf", "require"], function( angular, conf,
require) {
angular.module(conf.controllers, []);
require(["controllers/UsuariosCtrl",
"controllers/MyCtrl1",
"controllers/MyCtrl2",
"controllers/MainCtrl"]);
});

a controllers/MyCtrl1.js like this:

define(["angular","conf"], function(angular, conf){
angular.module(conf.controllers).controller('MyCtrl1', ['$scope',
'$window', function($scope, $window) {
$scope.btnTeste = function() {
$window.alert('clicou');
}
}]);
});

When I load the page I get:

Error: Argument 'MyCtrl1' is not a function, got undefined

My first thought was that was an angular problem, but if make the
MyCtrl1 declaration in main.js file it works.
My second thought was scope, and I've suspected the angular.js module
I've made:

define(['./js/lib/angular/angular.js'], function(){
if (typeof _ != 'undefined') {
_.noConflict();
}

if(typeof $ != 'undefined') {
$.noConflict() ;
}

return window.angular;
});

It just load angular file, and returns window.angular, and that's
where my head twists angular is a global object reachable through
window.
If i make a watch expression window.angular === angular on chrome it
says it's true.
requirejs here is just loading one angular file. Later I can put
another ones like angular-resource.js etc.

I hope this helps you somehow, and if some kind soul can help me I
would be very grateful, because I spent one entire week figuring this
out.
(Of course I'm learning about this amazing framework in the process).

Thanks in advance,

Geraldo Lopes de Souza

Geraldo Lopes de Souza

unread,
May 18, 2012, 7:44:37 PM5/18/12
to AngularJS
Correction

angular.module('myApp') locates the module.

I forgot: sorry my english mistakes :)

On 18 maio, 20:34, Geraldo Lopes de Souza <geraldo...@gmail.com>
wrote:

Cary

unread,
May 19, 2012, 1:36:19 AM5/19/12
to ang...@googlegroups.com
I've created a sample Angular/RequireJS app with separate controllers, services, directives, filters, and partials.

Please let me know if this helps.

Thanks,
Cary

Geraldo Lopes de Souza

unread,
May 20, 2012, 9:37:19 AM5/20/12
to AngularJS
Cary,

First, let me thank you for sharing your project with us.
It really helped.

I've adopted:
Module instantiation via requirejs

And figured by seeing your use of 'use' plugin (quite redundant
sentence) where the problem was.
I was using requirejs-jquery project that guarantees that jquery will
be loaded before requirejs since jquery has no support for amd.
But I was forgetting angular itself. Your project made me see this.

Now I'm using a couple files (http://tbranyen.com/post/amdrequirejs-
shim-plugin-for-loading-incompatible-javascript) to load them.

Like this:

jquery-amd.js
-------------
define(['order!./js/lib/jquery/jquery-1.7.2.js' ,
// jquery plugins not compatible with AMD
'order!./js/jquery.alpha.js',
'order!./js/jquery.beta.js'
], function(){
return window.jQuery;
});

angular-amd.js
--------------
define(['order!jquery','order!./js/lib/angular/angular.js'], function()
{
return window.angular;
});

and the configuration that I load at main page and refer to it at main
script
var appClinicaAmdConfig = {
baseUrl: '/js',
paths: {
angular: 'amd/angular-amd',
jquery: 'amd/jquery-amd',
order: 'lib/requirejs/order'
}
};


main.js
-------
require.config(appClinicaAmdConfig);
define(.....

This way when I require 'angular' the process occurs in three steps:
1) requirejs looks in the configuration and sees that it needs to load
amd/angular-amd.js which is a wrapper to insert angular on amd realm.
2) angular-amd, first loads jquery, but it waits for the loading to
complete through the use of the order plugin
3) angular itself is loaded and from now on when I refer to angular
that windows.angular will be returned

The advantage I see over the "use" plugin is that I make this just one
time, and the clients (the code that uses angular) doesn't need
to make nothing fancy to declare angular dependency.

With the use plugin, the problem is solved too, but you need to
remember to declare angular's dependency like this 'use!angular'

Regards,

Geraldo Lopes de Souza

On 19 maio, 02:36, Cary <c...@landholt.com> wrote:
> I've created a sample Angular/RequireJS app with separate controllers,
> services, directives, filters, and partials.https://github.com/CaryLandholt/AngularFun

Cary

unread,
May 20, 2012, 6:03:12 PM5/20/12
to ang...@googlegroups.com
Geraldo,

Good to hear you got things running.  However, RequireJS is now pushing to their 2.0 branch which deprecates support for the order and use plugins.

I've branched my AngularFun repo and am now using the new 2.0 RequireJS library.  The order plugin has no counterpart; however, the "shim" configuration takes the place of "use".
Details can be found on their wiki.

As a result, we no longer need to require dependencies with the ["use!{library}"] syntax, which sounds like you'd appreciate.  Me too, by the way.

It took just a minute to update my repo to the new changes.  Very slick.


Thanks,
Cary

Roberto Zuliani

unread,
May 21, 2012, 3:38:14 AM5/21/12
to ang...@googlegroups.com
Cary, thanks for sharing your project. My attempt, however, is to dynamically load controllers, not at the start of the application, but only the specific controller, when a route is selected (my application will have hundreds of controllers). My code refers to the registration of the controller after the file has been loaded via requireJs. Unfortunately this does not workDo you think there is a way to do it? 

2012/5/21 Cary <ca...@landholt.com>

--
You received this message because you are subscribed to the Google Groups "AngularJS" group.
To view this discussion on the web visit https://groups.google.com/d/msg/angular/-/jxq-v4sfwXoJ.

To post to this group, send email to ang...@googlegroups.com.
To unsubscribe from this group, send email to angular+u...@googlegroups.com.
For more options, visit this group at http://groups.google.com/group/angular?hl=en.

Renan T. Fernandes

unread,
May 21, 2012, 11:16:10 AM5/21/12
to ang...@googlegroups.com
I think the best way is to use a custom service to routing. I'm currently developing one(my first angular service :P) that load controllers only when needed, have namespaces, route validations(to see if makes sense to search a route inside of a namespace) and access validations(to see if the user can access the route), all this in a async way. But it isn't finished. I'm writing tests now, changing some things and haven't write any doc comment, but if you want I can put it on github so you can use it.

It currently size is 21k(development file), 14k(minified) and 3.7k(minified + gziped)

2012/5/21 Roberto Zuliani <arte.s...@gmail.com>



--
Renan Fernandes(aka "ShadowBelmolve")

Roberto Zuliani

unread,
May 21, 2012, 11:34:32 AM5/21/12
to ang...@googlegroups.com
It would be perfect, even without documentation. What you wrote is exactly what I'm trying to do. Thanks in advance 

2012/5/21 Renan T. Fernandes <re...@kauamanga.com.br>

Renan T. Fernandes

unread,
May 21, 2012, 12:17:20 PM5/21/12
to ang...@googlegroups.com
https://github.com/AngularBoss/boss-router

Yesterday I re-wrote it moving I great part of code from the global state to inside of provider and I haven't test after this so I'm not certain if is working correctly(no tests for routing yet).

The basic syntax for BRouteProvider is

BRouteProvider.root(). //create a new namespace for '/'
 .namespace("wiki"). //namespaces can create other namespaces
   controller("layout", "WikiLayoutController"). //set the controller WikiLayoutController for layout key(if the controllers is a string it will be evaluated after the route is selected and all scripts loaded
   template("layout", "/templates/wiki_layout.html"). //set the template for layout
   loadScripts("/controllers/wiki_layout_controller.js"). //this script will be loaded if a route inside of this namespace is selected
   .run(function(n) {  //a simply wrapper, this and r are both the namespace itself, the controller and loadScripts can be used here, there isn't difference
      n.match(":title").run(function(r){  //create a route that match with ^/wiki/(\w+)$
        r.controller("WikiPageController"); //if only argument is given it will be the controller of 'main'(default)
        r.as("page"); //it well make this route accessible via wiki_page helper. future implementations :)
        r.template("/templates/wiki_page.html"); //set the template for 'main'(1 argument == is the value of 'main' key)
      });
    });
BRouteProvider.notFound().  //route that is called if not route match, is a route exatcly equal is generated for namespace.match()
  redirectTo("/wiki/not_found"); //can use a function here, will receive the same arguments that routeProvider from angular send

in the html you simply put
<div boss-view-id="layout" class="boss-view"></div> //can set boss-view as element, attr or class(the code is borrowed from ng-view)

//templates/wiki_layout.html
....
<div class="boss-view"></div> //if not boss-view-id is set assume that is 'main'

with this you can create a app with nested layouts easily.

2012/5/21 Roberto Zuliani <arte.s...@gmail.com>

Charlie

unread,
May 31, 2012, 6:44:51 AM5/31/12
to AngularJS

thomas.fa...@googlemail.com

unread,
Aug 28, 2012, 9:11:03 AM8/28/12
to ang...@googlegroups.com
Hey Cary,
I tried your AngularFun-Example.
It was exactly what I was searching for. But I have my problems with using the angular test framework with your implementation.
How can I include the modules I need into the unit tests?

Thanks Thomas

Cary Landholt

unread,
Aug 30, 2012, 9:55:23 AM8/30/12
to ang...@googlegroups.com, thomas.fa...@googlemail.com
Thomas, sorry for the delayed response.
I've just committed support for Jasmine in my AngularFun repo (https://github.com/CaryLandholt/AngularFun).
You'll see a new tests directory with some setup files and one spec, twitterfySpec (I know, I know - my bad).
Just load the runner.html file in the browser and the unit test will execute.
To add unit tests, simply add your module to main.js.

Please let me know if this helps.

Thanks,
Cary

Eric

unread,
Aug 30, 2012, 12:28:52 PM8/30/12
to ang...@googlegroups.com, thomas.fa...@googlemail.com
Cary, 
is there any way to make the controllers include dinamically? i don`t wanna to use "When"...if i have a lot of controls i`ll lost my mind..rsrs

take a look:

var path, redirectPath, route;
      path = $location.$$path;
      if (path === "" || path === "/") {
        path = 'index';
      }
      route = {
        templateUrl: "views/" + path + ".html",
        dependencies: {
          configuracao: "scripts/" + path + ".js"
        }
      };
      $route.routes[path] = angular.extend({
        reloadOnSearch: true
      }, route);
      if (path) {
        redirectPath = (path[path.length - 1] === "/" ? path.substr(0, path.length - 1) : path + "/");
        $route.routes[redirectPath] = {
          redirectTo: path
        };
      }
      return $rootScope.$on("$routeChangeStart", function(scope, newRoute) {
        var $rt;
.....


can we do this?

Cary Landholt

unread,
Aug 31, 2012, 1:44:53 PM8/31/12
to ang...@googlegroups.com, thomas.fa...@googlemail.com
Hi Eric,
The short answer is yes.  You can load your controllers dynamically using RequireJS.  You can simply use the require('controller-module-name') syntax at some strategic point in your code, like when you're going to activate your route.  This would load the controller and any of its dependencies, assuming they're written as modules.
One thing to consider is that the controller won't be able to be included in the minified output as the RequireJS optimizer cannot discern the modules from dynamically-generated statements.

Cary

Pawel Kozlowski

unread,
Aug 31, 2012, 1:55:11 PM8/31/12
to ang...@googlegroups.com
Hi Cary!

Hmm, this is very interesting, especially this part:

> You can simply use the require('controller-module-name') syntax
> at some strategic point in your code, like when you're going to activate
> your route

I can see how one could download new modules with controllers at a
certain point in time (before route change seems very logical place)
but I don't know how to make AngularJS aware about new modules /
controllers. I saw some approaches but those were quite "brutal"
(boostraping AngularJS app from scratch). Did you manage to make it
work in practice? If so, what was the "magic" used to "inject" new
modules / controllers into a running application?

Would be grateful for any input!

Cheers,
Pawel
> --
> You received this message because you are subscribed to the Google Groups
> "AngularJS" group.
> To post to this group, send email to ang...@googlegroups.com.
> To unsubscribe from this group, send email to
> angular+u...@googlegroups.com.
> Visit this group at http://groups.google.com/group/angular?hl=en.
>
>



--
Question? Send a fiddle
(http://jsfiddle.net/pkozlowski_opensource/Q2NpJ/) or a plunk
(http://plnkr.co/)
Need help with jsFiddle? Check this:
http://pkozlowskios.wordpress.com/2012/08/12/using-jsfiddle-with-angularjs/

Looking for UI widget library for AngularJS? Here you go:
http://angular-ui.github.com/

Cary Landholt

unread,
Aug 31, 2012, 1:59:42 PM8/31/12
to ang...@googlegroups.com
Hey Pawel - I may have spoken too soon as I was only thinking how to dynamically load controllers via RequireJS, which is pretty trivial.  I have seen the brute force approaches you mention but have not attempted to make Angular aware of dynamically loaded controllers.

I'll give it a go, however.

Cary

Pawel Kozlowski

unread,
Aug 31, 2012, 2:48:56 PM8/31/12
to ang...@googlegroups.com
hi!

On Fri, Aug 31, 2012 at 7:59 PM, Cary Landholt <ca...@landholt.com> wrote:
> Hey Pawel - I may have spoken too soon as I was only thinking how to
> dynamically load controllers via RequireJS, which is pretty trivial. I have
> seen the brute force approaches you mention but have not attempted to make
> Angular aware of dynamically loaded controllers.
>
> I'll give it a go, however.

Please share if you manage to find anything... I was looking at the
$injector code few days back and I don't see any "official" extension
points to add dynamically loaded modules. In fact I recognize that
this is not simple since the config / run phase of the existing
(pre-loaded) modules was already executed and it is not obvious what
should happen with freshly loaded modules. For example, injecting
providers into configure doesn't make much sense since instances based
on those providers were already created. And there are more questions
like this...

As for now it looks like simply packaging / minifing everything is the
way to go, at least for people using modules...

I wonder how google guys handled this while rewriting double-click. As
far as I remember they were mentioning sth like 90k LOC, wonder how
big it can be after minification / gziping etc.

Anyway, share anything you can find :-)

Cheers,
Pawel

DesHartman

unread,
Sep 5, 2012, 5:33:12 AM9/5/12
to ang...@googlegroups.com, thomas.fa...@googlemail.com
Cary

Great fan of you requireJS videos and glad to see you are into AngularJS.

Love the idea of AngularFun, but not crazy about coffeeScript. I get the deal, I have heard the gospel on it, but for someone trying to understand Angular, this just makes the problem that much harder. Any chance you could branch the project to be JavaScript instead of CoffeeScript?

Thanks
Des


Cary Landholt

unread,
Sep 5, 2012, 8:48:03 AM9/5/12
to ang...@googlegroups.com, thomas.fa...@googlemail.com
Hey Des - thanks!
My original intent behind AngularFun was to create a project structure supporting my workplace needs (CoffeeScript, Less, Grunt, Node, Angular, and Require).  My team is creating a large Angular application with the aforementioned technologies.
In any case, I hear ya - not everyone's in to everything.  :)
I've been reading similar requests from others regarding CoffeeScript/JavaScript.
I'll go ahead and create a branch per your request and ping you back with the info.

Thanks again - always enjoy the feedback.

Cary

thomas.fa...@googlemail.com

unread,
Sep 18, 2012, 8:22:15 AM9/18/12
to ang...@googlegroups.com, thomas.fa...@googlemail.com
Hey Cary, 
I tried your test solution and it works fine.
Thank you very much.
Now I'm able to run Unit Tests.
Do you also have a way to run e2e Test scenarios?

Thomas

Cary Landholt

unread,
Sep 18, 2012, 8:24:07 AM9/18/12
to ang...@googlegroups.com, thomas.fa...@googlemail.com
Glad to hear, Thomas.  I have not tried e2e yet.  :(

Mateusz Bilski

unread,
Nov 17, 2012, 10:08:12 AM11/17/12
to ang...@googlegroups.com, arte.s...@gmail.com
Hi,
This is my attempt to solve the problem:


I would appreciate any comments.

Mateusz

Jens X Augustsson

unread,
Feb 21, 2013, 1:55:41 PM2/21/13
to ang...@googlegroups.com, arte.s...@gmail.com
Mateusz,

Thanks a bunch for your github code - nice solution. 

I made some small quick changes that also enabled me to lazy load any number of named Controllers when a view is needed and thus use any number of (possibly nested) Controllers (using ng-controller attribute) in my html views. Implementation could probably be better, I don't now much about RequireJS and the internals of AngularJS. 

This enables me to use server-side authorization (in this case a WAR), which will give anyone not having a certain "role" a HTTP 403 if trying to access /admin path, so I can physically hide controllers, directives and html for (authenticated but) unauthorized users. Sweeet.

Code is here:

Again - thank you, and thanks all for your brain powers.

/Jens

Kle Miller

unread,
Apr 17, 2013, 11:02:36 PM4/17/13
to ang...@googlegroups.com, arte.s...@gmail.com
Hi Guys,
I implemented the code from Jens - so thanks to both of you for your solution to dynamically loading the controllers/html/etc...

I am getting a strange error - first in firefox but now (about 3 days later) it has popped up in chrome where the ng:view just rendered 

function () { return html; }

which comes from the line in route-config 

routeDefinition.template = function () {
            return html;
        };

so sometimes instead of evaluating the function it just prints it out.

i haven't debugged why it might be doing this - one guess is slower than usual network loading, or loading too many resources caused the promise to screw up??

anyway i changed the code to 

routeDefinition.templateUrl = templateUrl; 

as template URL was already getting passed in. i haven't removed it from the require statement below but for the moment it has fixed the error. 

we (5 of us) will keep developing using this and keep you posted on how it is going, 

again, thanks for the code, 

Bhoomi Desai

unread,
Aug 9, 2013, 9:03:28 AM8/9/13
to ang...@googlegroups.com, arte.s...@gmail.com
I am using AngularJS + RequireJS + Karma. 

In my project I am able to load the controllers dynamically. The application is running fine.

But my unit Test fails. Test is able to load the controller file but the controller is not being registered in Angular and it throws error controller is not a function.

Pl help. Here is my github repo : https://github.com/bkak/AngularJSWithRequireJS

Thanks,
Bhoomi
Reply all
Reply to author
Forward
0 new messages