Lazy-load external JavaScript in one of AngularJS controller - angularjs

Some of my routes needs functionality from external JS. I don't want to load them all at once since those JS are needed only in certain routes (e.g. /upload needs some JS for photo uploading, /photos needs another JS for lightbox, /funny needs JS for animation stuff, etc).
What's the best practice for lazily loading those external JavaScripts?
Those routes can be accessed multiple times (e.g. user can go to /upload then /photos then /upload again)

In addition to what Alex has stated, if you will be lazy loading AngularJS artefacts such as controllers and directives, you would have to use the relevant provider to register them instead of the module API. Artefacts registered using the module API after the application has been bootstrapped, will not be available to the application. For example, you would define a lazy controller like this...
$controllerProvider.register('SomeLazyController', function($scope)
{
$scope.key = '...';
});
instead of this...
angular.module('app').controller('SomeLazyController', function($scope)
{
$scope.key = '...';
});
I have written a blog post detailing this as well as how to use the 'resolve' method that Alex speaks about, to implement lazy loading in AngularJS. http://ify.io/lazy-loading-in-angularjs/

The only way I know to handle cases like this is using the "resolve" method of a route. This method can be used to define a dependency to be loaded before the route's controller is instantiated. One of the different possible return types of this method is a promise. Thus you might use this to start loading your external JavaScript code asynchronously and return a promise that is resolved as soon as your external scripts are loaded.
The documentation for this can be found here: https://docs.angularjs.org/api/ngRoute/provider/$routeProvider in the "when" section.

#alex3683's answer is probably the correct AngularJS way, but I don't grasp the concept, so instead I make use of jQuery's getScript(). So, in CoffeeScript:
yourModule.factory 'foo', ->
$.getScript 'https://script-to-load', ->
# whatever you want to do once the script is loaded
And just call it from your controller. Since AngularJS services are lazy and singleton, this will only load the script once.

Related

Is there any way to share one object between two web pages in AngularJs without using $cookies

In AngularJs is there any way to share object between two web pages without using $cookies. For example my Login page controller have different angular.module and my other controller have different angular.module but I need to share credentials between Login page and another page without using $cookies.
Between independent page
You can use the standard HTML5 sessionStorage and localStorage object
For simple synchronous storage access HTML 5 introduces the localStorage attribute on the Window object:
localStorage["status"] = "Idling.";
LocalStorage is like cookie, sessionStorage will be clean when closing your browser.
In angular
You can use a factory which is technically a singleton. But if you refresh your page, all JS will be re-initialize and you will lose your data. A Service is also possible.
Here is a link on an other topic explaining difference between Services and Factory : AngularJS: Service vs provider vs factory
To create Services/Factory, give a look at Angular official documentation, it is well explained.
Perfect mix
What you need to do is create a Service, and at each modification you stringify it to store on a local/session Storage. On load, when angular create your service, you look in your storage for initialization value and your object is back.
This is quite common for authentification for exemple. You store a token to keep authentification when refreshing ;). Let me know if any difficulty to implement.
Have you considered creating a Service which stores these login credentials? You could then use Dependency Injection to have this Service available in both controllers.
Sharing the information between different pages is a critical task which can be done with the help of Controllers.
In AngularJS, we can share the data between controllers in three ways:
Factory: Object is created inside the factory and return it .
Service: With the service, you just have a standard function that uses the this keyword to define function.
Provider: With the provider, there’s a $get you define and it can be used to get the object that returns the data.

Is it possible to intercept html, css and js files used in angularjs?

I have already created an http interceptor and based on my observation, it only applies to all that uses $http service. What if I want to intercept even the css, html and js files used in the template? Is it possible?
This is the code of the interceptor I mentioned above:
app.factory('redirectInterceptor', function($q,$location,$window){
return {
'response':function(response){
if (typeof response.data === 'string' && response.data.indexOf("My Login Page")>-1) {
$window.location.href = "/login.html";
return $q.reject(response);
}else{
return response;
}
}
}
});
app.config(['$httpProvider',function($httpProvider) {
$httpProvider.interceptors.push('redirectInterceptor');
}]);
I want to be able to do an interceptor for css, html and js (for template) just like the one above.
As you said,
it only applies to all that uses $http service
So, if you want to intercept the requests for html,css and js files. It is best done on the server, rather than the client.
you can intercept html files
var requestInterceptor = function (config) {
if (config.url.indexOf(".html") != -1) {
//custom logic
}
}
though above does not work with .js or .css because they are loading using tag.
.html files are saved in template-cache and fires $http requests...
though you should go ahead and test what all being requested by doing console.log(config.url) in the interceptor to be sure..
This is a classic X-Y problem. You're thinking of a solution to your problem but it's the wrong way to go about it.
This line, from your comment, explains the actual problem:
but the js files that I'm referring to are the business logic of the project. So making it public is not an option.
So, your problem is you don't want to expose business logic to people who haven't/can't login.
There are several solutions to this. But it all boils down to one thing: separate your business logic from common js code used for UI etc.
Once you've separated your js into two parts (not necessarily two files but it must be at least two files, it can be more) you can expose your common js file(s) as public. Then you can decide how to handle loading your business logic:
Letting it Fail
This is probably the simplest strategy. Just do nothing and it will fail to load. Presumably you only need your business logic on logged-in pages so not having it in the landing or login pages should not be an issue.
Dynamicly Include it in the Template
Just omit the script tag that loads the business logic js if user is not logged in.
Load Business Logic Dynamically in JS
Either load it using AJAX then eval it or use require.js or use jQuery getScript() or something similar to load the business logic only when the user is logged in.
If you're creative you can probably come up with a couple more ways to handle loading the business logic JS. Whatever it is, the key is you need to make the common js and css files public.

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.

Lazy loading AngularJS modules with RequireJS

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.

Loading controller from partial

I would to dynamically load controller in my partial file so my code is better organized. Through my research, I found that if I want to load controller from partial using the script tag, I need to include JQuery.
However, these approach seem to only work if my controller is declared in the global scope, i.e.
function MainCtrl($scope) {}
If I switch to using module in my controller.js
angular.module ("myApp").controller ("MainCtrl", function ($scope) {});
this no longer work with the error message
"Argument 'MainCtrl' is not a function, got undefined"
Below is a plunker to demonstrate this.
http://plnkr.co/wNv3UD
How could I make this work?
Note
I did not include controller.js in index.html intentionally. I want to load controller from the partial.html, since it would only be used there.
Edit
I was able to achieve what I wanted to to after reading this question: AngularJS Dynamic loading a controller
This seem to be a straightforward approach to support lazy loading. Hopefully the $controllerProvider.register method could be exposed through angular.module.controller in future versions to support lazy loading.
You may want to take a look at [RequireJS][1]
it provides a good and easy way for you to load your .js files on the run.
for the dynamic controller loading part: you should write a provider (a service) which exposes some methods to register your controllers wile the angular app is running (take a look at $controllerProvider in angular docs)
i suggest you take a look at this post as it mentions how to fully customize your application regarding the script loading and controller registeration and stuff like that.
You can achieve this using custom directive and in directive you can load script using jquery getscript or jQuery ajax call, directive will fire when you load the partial

Resources