This problem has been taking up the last day or so.
I've been trying to get my AngularJS application to load the script files for each state's components lazily. I'm working on a large project at work in Angular, and the index.html
file has morphed into over 100 <script>
tags including the JS for the various controllers, services, and libraries. Most of them are small, so it isn't so much that loading time is a HUGE problem (though it could be), but it just never looked clean to me.
Maybe it's because I've gotten used to PHP's autoloader or have just been spoiled by all of the languages that can load their own dependencies at compile time. It's just not modular to have to load scripts for some minor, fringe state's directive in the root document of the application, or for the module that directive actually belongs to not to load the script itself if it's moved into another application without the <script>
list of glory.
Either way, I'm starting a new project and want to keep it cleaner, but loading components into Angular in this way presents a number of challenges. A lot of them have been addressed at one time or another in the documentation or some blog post, SO question, or another, but I've yet to see an end-to-end solution that integrates cleanly with other Angular components.
- Angular only bootstraps the
ng-app
directive if Angular and the modules are already loaded when the page is rendered. Even starting the application with lazy-loading requires a workaround. - The module API's methods only work before an application is bootstrapped. Registering new controllers, directives, filters, or services after the application has been bootstrapped, but after the scripts defining them have actually been loaded (and when they're actually needed) requires a workaround.
- Both lazy loading scripts and invoking AJAX-based services require the invocation of callbacks, and injecting the result of service calls into state controllers requires those services to actually exist to be called when the state transition starts. Actually INVOKING a lazily loaded service and resolving it before the state changes...requires a workaround.
- All of this needs to fit together in a way that doesn't look kludgy and can easily be reused in multiple applications without reinventing the wheel each time.
I've seen answers to #1 and #2. Obviously, angular.bootstrap
can be used to start up a module after the whole page has loaded without an ng-app
directive. Adding components after bootstrapping is a little less obvious, but saving references to the various $provider
services in the config blocks does the trick, overwriting the module
API more seamlessly so. Resolving #3 and doing it all in a way that satisfies #4 has been a bit more elusive.
The above examples solving #2 were for controllers and directives. Adding in services turns out to be a little bit more complicated, asynchronous ones, lazily loaded, and meant to provide their data to a lazily loaded controller especially so. With respect to Mr. Isitor, his code certainly works for registering a controller as a proof of concept, but the code is not written in a way that easily scales up to the kind of application for which lazy-loading the scripts makes sense, a much larger application with tens to hundreds of includes, dependencies, and asynchronous services.
I'm going to post the solution I came up with, but if anyone has suggestions to improve it or has already found a dramatically and radically different, better way, please feel free to add it on.
Here's the code for an Angular module
lazy
, depending on theui.router
module. When it's included in your module's dependencies, the lazy loading functionality of the state's scripts will be enabled. I've included examples of the primary app module, a few lazy components, and myindex.html
, sanitized for demonstration purposes. I'm using theScript.js
library to actually handle the script loading.angular-ui-router-lazy.js
index.html
Function Hacked into Script.js because I Prefer the Syntax
lazyapp.module.js
sectionservice.js
lazyheader.js
lazyappcontroller.js