Lazy loading AngularJS modules with RequireJS - angularjs

Thanks to the great article from Dan Wahlin, I managed to implement lazy loading of Angular's controllers and services. However, there does not seem to be a clean way to lazy load independent modules.
To better explain my question, assume that I have an app would be structure as below without RequireJS:
// Create independent module 'dataServices' module with 'Pictures' object
angular.module("dataServices", []).factory("Pictures", function (...) {...});
// Create 'webapp' ng-app, with dependency to 'dataServices', defining controllers
angular.module("webapp", ['dataServices'])
.controller("View1Controller", function (...) {...})
.controller("View2Controller", function (...) {...});
Here is the sample app with RequireJS in Plunker:
http://plnkr.co/aiarzVpMJchYPjFRrkwn
The core of the problem is that Angular does not allow adding dependency to ng-app post instantiation. As result, my solution is to use angular.injector to retrieve the instance of Picture object to be used in my View2Controller. See js/scripts/controllers/ctrl2.js file.
This creates 2 problems for me:
The injected services runs outside of angular and therefore all async call must end with $scope.$apply()
Messy code where some object can be injected using standard angular syntax while others require the explicit use of injector.
Have any of you figured out how to lazy load independent module using RequireJS and somehow hook this module in angular so normal angular dependency injection syntax can be used?
Note:
The question is on lazy loading of independent module. One simple solution to this specific example is to create "Pictures" object using cached $providers during ng-app.config but that is not what I am looking for. I am looking for solution that works with 3rd party module such as angular-resource.

I finalized my own implementation called angularAMD and here is the sample site that uses it:
http://marcoslin.github.io/angularAMD/
It handles config functions and out of order module definitions.
Hopefully this can help other looking for something to help them with RequireJS and AngularJS integration.

Take a look at my project in GitHub: angular-require-lazy
This project is intended to demonstrate an idea and motivate discussions. But is does what you want (check expenses-view.js, it loads ng-grid lazily).
I am very interested in comments, ideas etc.
(EDIT) The ng-grid Angular module is lazy loaded as follows:
expenses-view.js is loaded lazily, when the /expenses route is activated
expenses-view.js specifies ng-grid as a dependency, so RequireJs loads ng-grid first
ng-grid is the one that calls angular.module(...)
In order to accomplish this, I replaced (proxied actually) the real angular.module method with my own, that supports laziness. See bootstrap.js and route-config.js (the functions initLazyModules() and callRunBlocks()).
This implementation has its drawbacks that you should be aware of:
Config functions are not implemented (yet). I do not know if it is possible to lazily provide config-time dependencies.
Order matters in definitions. If service A depends on B but A is defined after B in your module, DI wil fail. This is because the lazyAngular proxy executes definitions immediately, unlike real Angular that makes sure dependencies are resolved before executing the definitions.

It looks like the Node.js module ocLazyLoad defines a way of doing this lazy-loading, though I'm not sure how it fares, performance-wise, compared to the methods in the other answers or hard-coding the dependencies. Any info on this would be appreciated. One interesting thing is that the other answers need RequireJS to operate, while ocLazyLoad doesn't.
It looks like ocLazyLoad defines another provider that injects the dependency after the containing module has already been instantiated. It seems to do this by essentially replicating some low-level Angular behavior, like module loading and providing, hence why it looks so complicated. It looks like it adds just about every core Angular module as a dependency: $compileProvider, $q, $injector, ng, and so many more.

Related

If I need to use IIFE, why doesn't Angular use it?

In John Papa's style guide he mentions the need to wrap every component in (function(){..}). This adds a couple lines to my code which makes me worried but not enough to justify a long work-around.
However, when I look at the actual Angular Code like the code for $compile and controller, I don't see these declarations.
Why is it important for me to isolate my variables with IIFE if Ng doesn't see it as important? Or a better question is why doesn't Angular implement IIFE?
Angular does use IIFE it wraps the code in
(function(window, document, undefined) {'use strict';
...
})(window, document);
So this distinguishes and protects the global/outer context and environment as a whole for all angular code
You can verify here
So it is wrapping the entire app right? But it isn't wrapping the
individual components
To answer your question, these components are not in separate files and are contained in single IIFE
the separate modules like angular-route, ng-resource are separated with angular.module() namespace or modular pattern from the global context
I think the answer may be, it depends.
First of all, AngularJS has to expose angular for the user to use, so it cannot be inside an IIFE. (just like jQuery exposes $ and jQuery, although it has an option that it only exposes jQuery).
And then, if you do decide to go with AngularJS, it won't be surprising that AngularJS expose some other stuff to the global space. But so far I have used AngularJS, I have only seen angular being exposed and all the things I do, I do it as
angular.module()
.controller()
.directive()
.factory()
.service();
and $compile is not exposed to the global space.
You only use IIFE to create a local scope so that the variables will not become part of the global scope.

Naming Angular modules

I'm trying to clean up my Angular app a little bit and when I came to my app.js.coffee file I ran into some things I have little knowledge of because I copy/pasted it from another source and it seemed to work.
To my knowledge every controller, service, filter etc. needs a ,
angular.module('nameOfModule')
But 2 (or more) controllers can't have the same module name because then Angular outputs an error,
Error: [ng:areq] Argument 'nameOfModule' is not a function, got undefined
But it seems the module name that you give a controller such as nameOfModule can be used on multiple services,
addMovieService.js
angular.module('addMovieseat')
.factory('movieAdd',
movieSearchService.js
angular.module('addMovieseat')
.factory('MovieSearch',
So I'm starting to wonder what the module names are for.
Add dependency for your angular module and if not then put it blank like this
angular.module('nameOfModule',[])
Error is occured because you don't add dependency for your module.
But 2 (or more) controllers can't have the same module name
This assumption is incorrect! Modules almost always have more than one controllers. In fact it is the highest level in an angular app and the module, along with all other entities (controllers, services, factories) comprise of a self sustaining AngularJS app.
For using another app inside another app, you just include the module as a dependancy like this:
angular.module('secondApp', ['firstApp'])

Breeze Angular Service Not Configuring Breeze to Angular Promises

I cannot get the Breeze Angular service (http://www.getbreezenow.com/documentation/breeze-angular) to configure Breeze to use Angular promises, i.e., I can never get the useNgPromises() function within breeze.bridge.angular.js to log a message to the console and thus I assume Breeze is never configured to use Angular promises.
My JS files are loaded as follows:
angular.js (v 1.3.14)
angular-route.js (v.1.3.14)
q.js (v 1.1.2) - This is here because I kept getting errors that Q was undefined if I left this out. I don't know if it is necessary to load Q.js if I use Angular promises instead.
breeze.js (v 1.5.3)
breeze.bridge.angular.js (v 1.1.0)
app.js and other app-specific JS files
So far, that satisfies steps #1 and #2 from the "Install It" section of that Breeze page.
Per steps #3 and #4 on that Breeze page and comments in breeze.bridge.angular.js, this is what my app.js looks like:
window.myApp = angular.module("myApp", [
"breeze.angular"
])
.value("breeze", window.breeze)
.config(["$routeProvider", function ($routeProvider) {
// Routing code
}])
.run(['breeze', function (breeze) {}]);
As far as I can tell, that code matches the Example #1 code on that Breeze page and satisfies steps #3 and #4 on that Breeze page.
Then I have some basic views that use controllers and datacontext JS files, the latter of which inject the breeze object as a dependency. To test whether Breeze is actually executing the configuration to work with Angular promises, I edited the breeze.bridge.angular.js file such that the following statement is included as the first line in the useNgPromises() function:
console.log("Using Angular promises!");
When I run my app, I never see that in the console, so I assume the configuration is not happening.
Am I doing something wrong in the setup? If so, what?
You absolutely do NOT need Q.js; the fact that you see errors relating to that library is an indication of a configuration mistake.
I think you found the problem when you added .value("breeze", window.breeze).
That one line wipes out the definition of the breeze service established by the 'breeze.angular' module (defined in breeze.bridge.angular.js), thus preventing the configuration of breeze for $http and $q. Without that configuration, breeze looks for a promises implementation elsewhere ... hence the complaint about missing Q.js. So ... yes ... that line was a disaster.
I wondered where you got the idea for that line. Thanks for referencing the Microsoft ASP Breeze/Angular template documentation. Wow that is old (2013). I'd kill it ... and the other defunct Visual Studio Breeze templates ... if I could (maybe I can). I thought we had made them hard to find. Looks like you found them. How?
That certainly isn't how we teach Breeze + Angular these days.
The "Todo Angular" sample is a far better guide (even if it is a year old).
I resolved the issue by commenting out the line:
.value("breeze", window.breeze)
I had included that line per the "Dependency Injection" section here: http://www.getbreezenow.com/ng-spa-template#module as well as the "Value Recipe" section here: https://docs.angularjs.org/guide/providers, but apparently it does not play well with the Breeze Angular service.
I am not sure exactly why this is the case but my best guess is that the issue was caused by Angular values not being configurable, per this explanation: https://gist.github.com/demisx/9605099, and I assume the Breeze Angular service requires the Breeze object to be configurable and therefore it should not be injected as a dependency for the app via the value recipe.
I should also note that after removing that troublesome line of code, I no longer receive an error indicating that Q.js is required since the Breeze Angular service successfully configures Breeze to use Angular's $q instead.

Registering AngularJS components via providers

I'm implementing an Angular/RequireJS routing solution based on Dan Wahlin's article.
In the article, Dan makes the following register shortcuts on his app object:
app.register =
{
controller: $controllerProvider.register,
directive: $compileProvider.directive,
filter: $filterProvider.register,
factory: $provide.factory,
service: $provide.service
};
When I use these, I can correctly register and reference my controllers through RequireJS calls, but if I use the traditionall app.controller() or angular.module('myApp').controller() syntax, then Angular can't find them and I get errors from the router.
How is defining controllers, directives, etc. with the above method different, and why does it work with RequireJS where the more traditional method does not?
Any ideas?
Thanks,
-Nate
Since the controllers are being added dynamically things have to change a bit from the "norm" (unfortunately). The main difference is that controllers are being wrapped in RequireJS modules, being downloaded dynamically, and then being registered. The $controllerProvider.register allows for the dynamic registration. It's definitely not the normal technique but what's required in this scenario. That's why "app" (which is the RequireJS module that gets us to the application's AngularJS module) is passed in to all of the controller modules. It exposes the controller property shown above which handles the registration "on the fly".
If you download a controller script dynamically and then use the normal technique (angular.module('..').controller(..)) it won't register it properly - at least that was the case the last time I tried it. It's been several months since I've tried it but I'm assuming the same behavior is still there.
The bottom line is that when controllers (and other items such as services/factories) have scripts that are loaded "on the fly" things change somewhat and the way you access and register these items changes from the normal way that we're all used to seeing.

Is it possible to create an AngularJS service without a module?

In AngularJS there is a short form to create controllers, so you do not have to use a module for this. This short form is to simply define a function
function FooController ($scope) {
// ...
}
and reference it from the HTML markup using ng-controller:
<div ng-controller="FooController">
...
</div>
Now, my question is, whether there is a similar "short form" to define services? In other words: Can you define (and use) a service without using a module?
Please note that I know that it perfectly makes sense to structure your applications using modules, and not doing so could / should be regarded as bad practice. It's just that I am explaining AngularJS to someone who's completely new to it, and the question arose today. So it's just for curiosity and completeness.
Quoted from the doc of Creating Services
To register a service, you must have a module that this service will
be part of.
So I guess the answer is no.
Since the ng module is always loaded, you could define your service on the ng module:
angular.module('ng')
.service('aService', function () {
// you really shouldn't do this
});
It works, but you shouldn't be messing with a module that you don't own.
If you're trying to explain to someone why using your own modules with .controller is good:
Talk to them about testing. It's a lot easier to mock things if you're using modules
If you need to have other modules as dependencies (like angular-ui)
If you want to decorate another service (e.g. $http)
If you need to register directives, filters, animations, etc.
It's a lot nicer to have modular code :)
It's an angular concept I don't understand at all, I always believed "separation" is good, so I dont get why we need to associate a service to a specified application.
Here what I am doing: you can declare your angularjs application as a global object:
window.markrApplication = angular.module('markrOptionsApp', ....)
And declare all your services like this:
markrApplication.service('data', ....
Of course you need to have all the dependencies set on all your angularjs application.

Resources