1

I'm trying to create an NG app where parts can be enabled/disabled dynamically. The idea is to have an "admin" page, where parts of the app can be enabled or disabled, and then see new functionality appear, in the form of an adjusted menu at the top of the page, and matching routes, controllers, etc loaded into the app (I'm using SocketStream w/ NG).

The first step was to add / remove routes dynamically, for which I found a solution at https://stackoverflow.com/a/13173667 - working well, as far as I can tell.

Next, adding items to the menu bar - easy with ng-repeat on ul/li items.

So the app adjusts its menu and recognizes the corresponding route. So far so good.

The problem comes with registering a controller. I'm calling myApp.controller('SandboxCtrl',[...]) with proper args (same as what worked when initialising statically on startup), but the controller does not appear to get loaded or inited properly. Navigating to the newly added route generates errors such as:

Error: Argument 'SandboxCtrl' is not a function, got undefined
assertArg@http://cdnjs.cloudflare.com/ajax/libs/angular.js/1.1.1/angular.js:973
assertArgFn@http://cdnjs.cloudflare.com/ajax/libs/angular.js/1.1.1/angular.js:984
@http://cdnjs.cloudflare.com/ajax/libs/angular.js/1.1.1/angular.js:4638
update@http://cdnjs.cloudflare.com/ajax/libs/angular.js/1.1.1/angular.js:14007
$broadcast@http://cdnjs.cloudflare.com/ajax/libs/angular.js/1.1.1/angular.js:8098
@http://cdnjs.cloudflare.com/ajax/libs/angular.js/1.1.1/angular.js:7258
wrappedCallback@http://cdnjs.cloudflare.com/ajax/libs/angular.js/1.1.1/angular.js:6658
wrappedCallback@http://cdnjs.cloudflare.com/ajax/libs/angular.js/1.1.1/angular.js:6658
@http://cdnjs.cloudflare.com/ajax/libs/angular.js/1.1.1/angular.js:6695
$eval@http://cdnjs.cloudflare.com/ajax/libs/angular.js/1.1.1/angular.js:7848
$digest@http://cdnjs.cloudflare.com/ajax/libs/angular.js/1.1.1/angular.js:7713
$apply@http://cdnjs.cloudflare.com/ajax/libs/angular.js/1.1.1/angular.js:7934
@http://cdnjs.cloudflare.com/ajax/libs/angular.js/1.1.1/angular.js:5433

I'm currently at a loss on how to proceed. I've not been able to find a solution on the web. The app is too large to put in a jsFiddle, but I can commit the last changes on GitHub if needed.

Questions: is this feasible? what can I do to debug this? any examples I could look at?

EDIT: The code is now at https://github.com/jcw/housemon (needs node/npm/redis). It's easy to reproduce the problem: launch with "npm start", browse to localhost:3333, go to admin tab, click on "jcw-sandbox" and then "Install". Top menu will update with new a "Sandbox" entry. Clicking on that entry generates the error shown above.

Oh, almost forgot: relevant code is in client/code/app/main.coffee and client/code/modules/routes.coffee ...

Community
  • 1
  • 1
jcw
  • 31
  • 2
  • 1
    tough to help if no code is provided. – mpm Jan 22 '13 at 02:23
  • Are you loading the JavaScript file containing the SandboxCtrl? – WiredPrairie Jan 22 '13 at 02:33
  • Hi, you need to use $controllerProvider in order to register your lazy loaded controller. I have working solution for loading controller on demand (with RequireJS) and registering them. Check out my repository here: https://github.com/matys84pl/angularjs-requirejs-lazy-controllers – matys84pl Jan 22 '13 at 07:51
  • @camus - fair enough, I've committed the code on [github](https://github.com/jcw/housemon) – jcw Jan 22 '13 at 09:49
  • @WiredPrairie - the file is available via SocketStream, which uses browserify. Don't understand much of how that works, alas. I just put the files in the proper place in the clients/ area, and can then `require` them as in node. – jcw Jan 22 '13 at 09:52
  • @matys84pl - aha, that looks very interesting, thx. My controller/directives understanding is still limited. I'm assuming RequireJS is similar to Browserify. Is `app/js/utils/route-config.js` the key to understanding how your code works? – jcw Jan 22 '13 at 09:57
  • Yes, sorry that I didn't pointed you the files.. it all starts in app.js when you have to get hold of those providers that are available only during config phase. Then the registering is done in route-config.js – matys84pl Jan 22 '13 at 10:09
  • @matys84pl - Ok, you're solving a more advanced problem - with async require and a promise to install the controller. I hope to get there too, but first step is to assume that all code is already on the client side (`require` is instant, all modules already cached). What is the role of $compileProvider, and why do I not need it when loading controllers in the normal way on app startup? – jcw Jan 22 '13 at 10:56
  • You meant $controllerProvider? normally during bootstrap process Angular does the controller registration for you, however if you need to do it manually you need to get hold of the $controllerProvider and call register(..). Basically calling myApp.controller(...) won't work it later stages - that's why most of the people try to figure out how to lazy load the controllers as this is not straight forward in Angular - my idea was to use $controllerProvider. – matys84pl Jan 22 '13 at 11:08
  • Thanks, that helps. I'll do some more reading now. BTW, one part I forgot was to use $scope.$apply in the SocketStream callback. Change committed to GitHub. Solves the error, but controller is not yet doing anything. Probably exactly the issue you describe. – jcw Jan 22 '13 at 11:22
  • Might be going down a rabbit's hole, but I saw that NG's source uses "invokeLater" to push controller registrations to an _invokeQueue in the app module. Then `loadModules` in `injector.js` will perform the delayed requests. Stashing away $controllerProvider seems like a good way to avoid going this deep, but now I'm wondering if the same has to be done for lazy filters, directives, and services too? – jcw Jan 22 '13 at 14:35

1 Answers1

0

The answer turns out to be two-fold:

  • the NG calls were made from SocketStream RPC callbacks, and had to be wrapped in $scope.$apply calls - my bad, didn't know about this SS/NG interaction
  • the rest of the solution was outlined by @matys84pl - pick up $controllerProvider (and $filterProvider) early on, so they can be called at a later time instead of the normal "app.controller" and "app.filter" members, which don't seem to work anymore later on

Example code in GitHub, I'll link to a specific commit so this answer stays valid:

https://github.com/jcw/housemon/commit/f199ff70e3000dbf57836f0cbcbb3306c31279de

jcw
  • 31
  • 2