Injecting modules after lazy loading - angularjs

I a have a chase where I have large application and in an attempt to keep the size of it down I am trying to lazily load submodules and their associated assets. I am using ocLazyLoad to lazyLoad the modules and their files.
However, after a module is loaded none of it dependencies seem to get registered into the application, so services and directives the lazily loaded submodule depend on are not loaded resulting in things not looking the way they should.
This plunk provides a minimal example.
//lazilyLoaded.module.js
angular.module('lazyLoadedModule',
['orginallyIncludedModule'])
.run(function(){
console.log('lazyLoadedModule ran');
});
//script.js
angular.module('app', ['oc.lazyLoad']).config(['$ocLazyLoadProvider',
function($ocLazyLoadProvider) {
$ocLazyLoadProvider.config({
debug: true,
modules: [{
name: 'lazyLoadedModule',
files: ['lazyLoaded.module.js']
}]
});
}]).run(function($ocLazyLoad){
$ocLazyLoad.load('lazyLoadedModule');
});
//script.js
angular.module('orginallyIncludedModule', [])
.run(function(){
console.log('originallyIncludedModule ran');
})
.directive('simpleDirective', function(){
return {
template: '<p>All is well</p>'
};
})

There's a couple of things going on here. First is that you need to wait until the module is loaded before attempting to use it but also secondly you need to tell the page that a new directive is available. This isn't well documented but is in the FAQS.
I lazy-loaded a directive but nothing changed on my page
If the directive was in your DOM before you lazy-loaded its definition, you need to tell Angular that it should parse the DOM to recompile the new directive. To do that you need to use $compile :
Here's how I changed your run method
run(function($ocLazyLoad, $compile, $rootScope, $document){
$ocLazyLoad.load('lazyLoadedModule').then(function() {
$compile($document[0].body)($rootScope);
});
});
This causes your directive to run. Accessing the DOM in the run method is not very angular, so the above is a bit of hack but at least gets you started.

Related

Golden Layout | Error: ng:btstrpd App Already Bootstrapped with this Element

I am using goldenlayout with angualrJS. I am facing below exception:
Error: ng:btstrpd App Already Bootstrapped with this Element
on execution of this line of code
myGoldenLayout.on('initialised', function () {
angular.bootstrap(angular.element('#layoutContainer')[0], ['app']);
});
The reason is, I have already ng-app in my HTML so how can I register golden layout when I already have ng-app?
https://github.com/codecapers/golden-layout-simple-angular-example/issues/1
Well, the official Golden Layout docs recommend using manual bootstrap, but if you want to keep using ng-app, then you have to make sure that your components (templates) are compiled by Angular (via $compile). Here's an example of how to do that:
angular.module('someApp') // your main module name here
.run(function ($compile, $rootScope) {
myLayout.registerComponent('template', function( container, state ){
var templateHtml = $('#' + state.templateId).html();
var compiledHtml = $compile(templateHtml)($rootScope);
container.getElement().html(compiledHtml);
});
myLayout.on( 'initialised', function() {
$rootScope.$digest(); // Golden Layout is done, let Angular know about it
});
});
// somewhere...
myLayout.init();
Basically, the main difference from the example in the repository you provided is that instead of just appending raw HTML, we $compile it with Angular, so now it knows to set up bindings and keep the html updated.
This should allow you to keep using ng-app instead of manual bootstrap.

Difference between app.register.controller and app.controller in AngularJS

I don't know when to use app.register.controller and app.controller to create controller after module is created. I have googled but I didn't find clear difference between two scenarios. please post sample example.
Simple Answer
You can use app.controller to register providers before the app run (i.e., before bootstrap or ng-app, at config time). And app.register.controller is used to register a new provider when the app has already been bootstrapped (i.e., it's running).
A More Elaborated Explanation
AngularJs loads all providers that were registered before the module gets bootstrapped, once your module gets bootstrapped, angular won't look for registered providers anymore. It's fine for most apps, but, in some cases, you will have to load new providers at run time (i.e., after the app gets bootstrapped), that's called lazy loading. Therefore, provided that angular won't look for registered components anymore, you will have too register it manually.
For example:
var app = angular.module('myApp', []);
app.controller('myController1', function (){});
angular.element(documento).ready(function () {
// equivalent to ng-app attribute
angular.bootstrap(document, ['myApp']);
});
At this point, angularjs will load all providers registered before the bootstrap phase. However, if you try to register a controller again, it won't get loaded on your application, because angularjs loads it just when bootstrapping the app.
So, to register a provider at run time, you have to expose the angularjs' provider and component factories on your module like so:
app.config(function($controllerProvider, $compileProvider, $filterProvider, $provide) {
app.register = {
component: $compileProvider.component,
controller: $controllerProvider.register,
directive: $compileProvider.directive,
filter: $filterProvider.register,
factory: $provide.factory,
service: $provide.service
};
});
Check this answer for more info: https://stackoverflow.com/a/20922872/4488121
Finally, now it allow you to register a provider after the app bootstrap (i.e., at run time).
app.register.controller('myController2', function (){});

AngularJS - What is expected behaviour of two directives with the same name but in different modules?

Of course, I can check it myself.
It's more conceptual/architectural question and why it was build so.
angular.module('appmodule1').directive('mydir', function(){});
angular.module('appmodule2').directive('mydir', function(){});
so what should we expect from mydir?
UPD:
dependencies between the modules:
angular.module('app',['appmodule1', 'appmodule2'])
or
angular.module('appmodule1', ['appmodule2']);
One trivial thing is that if your module only directly/indirectly loads only one of the module then that directive factory only will be used. But if your question is what if both the modules are loaded say for example angular.module('app',['appmodule1', 'appmodule2']) and your application is bootstrapped with the module app then the directive factories will be added together, i.e directive factories are additive and such component when used will render with both the factories.
angular.module('app1', []).directive('mydir', function() {
return {
restrict: 'E',
link: function() {
console.log("App1");
}
}
});
angular.module('app2', []).directive('mydir', function() {
return {
restrict: 'E',
link: function() {
console.log("App2");
}
}
});;
angular.module('app', ['app1', 'app2']).config(function($provide) {
$provide.decorator('ngRequiredDirective', function($delegate) {
console.log($delegate); //Check the console on the configuration you will see array of 2 configurations
return $delegate;
})
});;
<script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.4.0/angular.min.js"></script>
<div ng-app="app">
<mydir></mydir>
<test ng-required="true"></test>
</div>
Generally the scoping, template etc cannot be specified in both (Rules are same as when an element has more than one directives mentioned) and these kind of directives are generally defined in the same module (or even in a different module) with intend and for special purpose. For example angular internally has a directive with the selector select which works along with ng-options directive, now say in your application you want to convert all the select to a bootstrap select option or with ng-repeated material select. You can abstract them out an still create another directive with the selector select and add your logic to render it by parsing the ng-options expression and render the new dropdown.
An example is within angular itself, the way ng-required and other similar directives are implemented, see this answer for example. You can check it out by doing a console log of ng-required directive factory as below.
.config(function($provide) {
$provide.decorator('ngRequiredDirective', function($delegate) {
console.log($delegate); //Check the console on the configuration you will see array of 2 configurations
return $delegate;
})
});
Why it was built?
By bet would be on extensibility and dividing different responsibility in different factories.
So in short if at all you have multiple directive factories for the same selector it should not be accidental, but created with clear purpose, otherwise it could turn out to be a disaster!
It will depend under which module the directive is instantiated. If you're under the appmodule1, the corresponding directive would be used. There would be no conflict here, unless I'm missing something.

How to use angular injection with a dynamically loaded controller

How do I get a dynamically loaded template and controller injected? After loading my .html partial and .js controller, I would assume the next step is $injector? But how do I use it? something like this...?
My progress so far:
http://plnkr.co/edit/rB5zteYTZ2L1WB5RzlHg
data is returned from a $http.get()
var $injector = angular.injector(['ng']);
$injector.invoke(function($rootScope, $compile, $document) {
$compile(data)($rootScope);
$rootScope.$digest();
});
What format does the Controller.js file need to be in for the injector/compiler to wire it up correctly? Can I simply do what I did in Plunker?
Controller1.js
app.controller('Controller1', function ($scope, $famous) {
console.log("Inside Controller1");
});
Note: I am specifically trying to avoid using requirejs, ng-route, ui-route, ocLazyLoad, etc. I would like to understand the very basics of what these packages accomplish for routing and dynamic loading of a view/controller.
I am not sure if I totally understand your question but it looks like you want to load views and controllers dynamically. I am using a combination of angular ui router and angularAMD. It works very smoothly and with that approach you get a nice separation and on-demand loading.
From angular ui router webpage:
templateUrl: "partials/state1.list.html",
controller: function($scope) {
$scope.items = ["A", "List", "Of", "Items"];
}
With that configuration the specified controller will get loaded and connected to the state1.list.html.
Does that help?

Dynamically Loading Controllers in AngularJS for routing

I need to load controllers dynamically from certain directories, i.e. instead of this:
$routeProvider.when(path, {
templateUrl: 'templates' + path + '.html',
controller: 'myController'
});
the directory where it is placed should be pointed:
$routeProvider.when(path, {
templateUrl: 'templates' + path + '.html',
controller: 'controllerDir/myController'
});
I guess there should be a some standard approach for it(?). May be there is some way to use dynamically loading file containing the controller similar to the qQuery fashion (getScript)?
To dynamically load controllers, you need to use RequireJS.
http://requirejs.org/
There is an util that will make it easier to integrate RequireJS to AngularJS app
https://github.com/marcoslin/angularAMD
Route has a resolve property which can be exploited to achieve dynamic loading of controllers.
This link should get you started:
http://weblogs.asp.net/dwahlin/dynamically-loading-controllers-and-views-with-angularjs-and-requirejs
On a different note, consider this dynamic loading of scripts only when you have lot of controllers. It's better to load scripts upfront (which can be bundled and minified) and have browser cache them to give a smooth experience to users
You can also use this method: https://github.com/kapysh/angular-dynamic-loader
Just grab the directive from that repo loadctrl.directive.js
and in your template:
<div load-ctrl="portalController"></div>
and in app.js:
portal.constant('CONTROLLER_PATHS', {
'portalController': {
path: 'static/angular_app/js/controllers/portal.controller.js',
loaded: false
}
})

Resources