Angularjs module dependencies loop - javascript

I am curious and still can not explain dependency loop (passed) in this case.
angular.module('mainApp', ['addon.dashboard']).factory('Test', function(){
return {
hello: 'moto'
};
});
angular.module('addon.dashboard', ['mainApp']).controller('Demo', function(Test){
console.log(Test.hello);
});
That is a sample code in my app. The mainApp module require to inject addon.dashboard module. Otherwise, addon.dashboard module require to inject mainApp module.
We can see that it may loop here. But it work in my app. The controller Demo actually output moto into console.
How does angular deal with loop injection like that?

You might want to look into the angular code (especially the loadModules method). Basically there is a HashMap that contains all the modules loaded. If its not in the HashMap, it puts it into that and then goes ahead with initializing the rest of the module. IF its already in the set, it will return immediately.
So, in your case, lets say mainApp gets loaded first. It puts it into loadedModules and the goes to find its dependencies. When it finds addon.dashboard, it finds that mainApp is a dependency, but its already present in loadedModules, so it returns immediately.
Its a lot better if you breakpoint into the "loadModules" method of angular.js

Related

Delete or Redefine angular module

with the following I would like to be able to define a module the first time and then change it on the fly later on. The reason is that my modules are dynamically generated and I'd rather reload one module than the whole app once One JavaScript file changes; it is Huge and Slow to load. The JS files are watched for changes and loaded lazily but always the initial module persists, i.e. 'This will be logged the first time' always gets logged even though the new JavaScript executes it does not overwrite the old module.
I think this would be a great tool for the AngularJS community but I don't know as yet it if it's already possible (I keep reading it's not). Alternatively I'd be happy if I could achieve the same using controllers/directives/1.5components
Thank you
// First state of angular module
angular.module('myApp.view1', [])
.controller('View1Ctrl', ['$scope',function($scope) {
$scope.buttonClick = function () {
console.log('This will be logged first time');
}
}]);
// Second state of angular module
angular.module('myApp.view1', [])
.controller('View1Ctrl', ['$scope',function($scope) {
$scope.buttonClick = function () {
console.log('This will be logged the second time');
}
}]);
<button ng-click="buttonClick()">Button</button>
However that's not a groovy thing to redefine your angular module.
You can do that by just defining the angular.module('YourAppModule', ['Dependencies']);
This will overwrite your previously bootstrapped module.
I have made a feature request for this issue, but unfortunately it's been denied, understandably so. Further details are here: https://github.com/angular/angular.js/issues/14743#issuecomment-224868610

Isolation with Angular on karma jasmine

I have a set of tests that are spread across 2 modules.
The first module has tests and for it's dependencies i declare mocks to test it without any influence from it's dependent module, like so:
beforeEach(function(){
angular.mock.module(function ($provide) {
$provide.value("mockServiceFromModule1", mockServiceFromModule1);
$provide.value("mockServiceFromModule2", mockServiceFromModule2);
});
angular.module('module1', []);
angular.module('module2', []);
angular.mock.module('moduleThatIAmTesting');
angular.mock.inject(function (_$rootScope_, _$q_, _$httpBackend_, ..., somethingFromTestModule) {
});
})
The second module has a series of tests and all of them pass when i run only them.
beforeEach(function(){
angular.mock.module('module1');
angular.mock.inject(function (_$rootScope_, _$q_, _$httpBackend_, ..., somethingFromModule1) {
});
})
Both tests when run with f(Running only them) works, but when i run the whole test suit i get errors, specially regarding module declaration or $httpBackend.
How can i make jasmine run each test as if they were the only tests?
It seems i am messing with the angular/modules/$httpBackEnd on each test and the changes are being propagated when it starts a new test.
Update 1
I have a jsFiddle representing the issue .
The structure of the problem is :
Some test is ran with a mock dependant module
Later another test wants to test the actual mocked module
Since the first moldule was already loaded we can't overwritte it and the test fails.
On the JSFiddle the error about $httpBackend without nothing to flush is because the request for the expectedGet is never hit, and it's never hit because of the previously loaded empty module
It's important to notice that the ORDER of the tests is the only thing relevant to failing as in this JSFiddle with the same tests they pass.
I could of course make a tests order and bypass this but i am aiming to find a way to do the tests with isolation without worrying about other tests side effects.
The problem you are experiencing is due to the nature of how the $compileProvider handles a new directive being registered with a pre-existing name.
In short; You are not overriding your old directive, you are creating a secondary one with the same name. As such, the original implementation runs and tries to grab baz.html and $httpBackend throws as you have not set up an expectation for that call.
See this updated fiddle that did two changes from your original fiddle.
Do not inject the parentModule to your childModule spec. That line is not needed and it is part of the reason you are seeing these errors. Oh and angular.module is evil in the land of tests. Try to not use it.
Decorate the original directive if you wish to roll with the same name as the original one, or name it something else. I've opted for naming it something else in the fiddle, but I have supplied code at the end of my answer to show the decorator way.
Here's a screenshot of what happens in the following scenario:
Module A registers a directive called baz.
Module B depends on module A.
Module B registers a directive called baz.
As you can probably imagine, in order for the module system to not insta-gib itself by letting people overwrite eachothers directives - the $compileProvider will simply register another directive with the same name. And both will run.
Take this ng-click example or this article outlining how we can leverage multiple directives with the same name.
See the attached screenshot below for what your situation looks like.
The code on lines 71 to 73 is where you could implement solution #2 that I mentioned in the start of my answer.
Decorating baz
In your beforeEach for testModule, replace the following:
$compileProvider.directive('baz', function () {
return {
restrict: 'E',
template: '{{value}}<div ng-transclude></div>',
controllerAs: 'bazController',
controller: function ($scope, fooService) {
$scope.value = 'baz' + fooService.get()
},
transclude: true
};
});
With this:
$provide.decorator('bazDirective', function ($delegate) {
var dir = $delegate[0];
dir.template = '{{value}}<div ng-transclude></div>';
dir.controller = function ($scope, fooService) {
$scope.value = 'baz' + fooService.get();
};
delete dir.templateUrl;
return $delegate;
});
jsFiddle showing the decorator approach
What about the call to angular.module('parent', [])?
You should not call angular.module('name', []) in your specs, unless you happen to be using the angular-module gist. And even then it's not doing much for you in the land of testing.
Only ever use .mock.module or window.module, as otherwise you will kill your upcoming specs that relate to the specified module, as you have effectively killed the module definition for the rest of the spec run.
Furthermore, the directive definition of baz from parentModule will automatically be available in your testModule spec due to the following:
angular.module('parent', []).directive('baz', fn());
angular.module('child', ['parent']);
// In your test:
module('child'); // Will automatically fetch the modules that 'child' depend on.
So, even if we kill the angular.module('parent', []) call in your spec, the original baz definition will still be loaded.
As such, the HTTP request flies off due to the nature of $compileProvider supporting multiple directives with the same name, and that's the reason your spec suite is failing.
Also, as a last note; You are configuring undefined modules in your beforeEach blocks. If the goal is to configure the module of your test, you are in the wrong.
The syntax is as follows:
mock.module('child', function ($compileProvider, /** etc **/) {
});
// Not this:
mock.module('child');
mock.module(function ($compileProvider, /** etc **/) {
});
This can be seen in the screenshot I posted. The $$moduleName property of your mocked baz definition is undefined, whereas I am assuming you would want that to be child.

how to reference an angular value so that reference does not get destroyed on minification

The following code is working fine before minifiaction. Essentially, editableOptions is an angular value provided by the angular-xeditable library.
angular.module('common').run(function(editableOptions) {
editableOptions.theme = 'bs3';
}
)
However, when I minify, I get an injection error. I believe this is because editableOptions is getting minfied. How can I reference it differently so that this does not occur? Is there some way of referencing it starting with angular.module('xeditable') ?
Use the minification-safe method of defining injected objects:
angular.module('common').run(
['editableOptions',
function(editableOptions) {
editableOptions.theme = 'bs3';
}
]);
Angular team have a solution to this problem mentioned in one of their tutorials:
https://docs.angularjs.org/tutorial/step_05
A Note on Minification
Since Angular infers the controller's dependencies from the names of arguments to the controller's constructor function, if you were to minify the JavaScript code for PhoneListCtrl controller, all of its function arguments would be minified as well, and the dependency injector would not be able to identify services correctly.
We can overcome this problem by annotating the function with the names of the dependencies, provided as strings, which will not get minified.
This is another style to avoid minification and uglification problems:
function runModule(editableOptions) {
editableOptions.theme = 'bs3';
}
runModule.$inject = ['editableOptions'];
angular.module('common').run(runModule);
Todd Motto Angular Style
Guide
This aids with readability and reduces the volume of code "wrapped"
inside the Angular framework

Inject sub module to main module

I want to inject my sub module to main app, but I have injection error
(Error: [ng:areq] http://errors.angularjs.org/1.3.5/ng/areq?p0=SelectionCtrl&p1=not%20aNaNunction%2C%20got%20undefined
it's my main app
and it's my sub module
How can I fix that? Thanks!
You are messing up with module declaration. You declared angular.module('app.newProject') two times.
While creating it first time you registered SelectionCtrl. After that you created another module with same name angular.module('app.newProject,[]') with dependancy and registered TabController1 controller. When you created second module it overrides first one & now it has only TabController1 thats why angular is throwing error SelectionCtrl is required.
There are several appraoches resolve this approach.
Approach 1
Create a module and store it in some variable and use it whenever you want.
var controllerApp = angular.module('app.newProject', [])
.controller('SelectionCtrl',function(){
//code here
});
controllerApp.controller('TabController1',function(){
//your code here
});
Approach 2
Create a module, and whenever you want to use it, use it without dependency.
angular.module('app.newProject', [])
.controller('SelectionCtrl',function(){
//code here
});
angular.module('app.newProject').controller('TabController1',function(){
//your code here
});
Approach 3 (I wouldn't prefer this approach)
Create a module and append you components in linear manners.
angular.module('app.newProject', [])
.controller('SelectionCtrl',function(){
//code here
})
.controller('TabController1',function(){
//your code here
});
I would prefer you to go for Approach 2 which will provide you to bind components any by referring a module.

AngularJS name collision in Dependency Injection?

Say I have two modules (finance2, finance3), and each of them defines a service with the same name (currencyConverter).
If I tell my main module that it depends on just finance2, I can inject the service like this:
angular.module('invoice2', ['finance2'])
.controller('InvoiceController', ['currencyConverter', function(currencyConverter) {
However, if I want my invoice2 to depend on both modules, which currencyConverter would be injected? The one from finance2 or the one from finance3? I can control my own modules, but my concern is if you rely on other people modules that define factories with the same name. How does angular deal with that?
angular.module('invoice2', ['finance2','finance3'])
.controller('InvoiceController', ['currencyConverter', function(currencyConverter) {
The last one loaded/processed will win.
If your scripts are:
finance2 (with currencyConverter)
finance3 (with currencyConverter)
Then you will get finance3's currencyConverter when the dependency for currencyConverter is resolved.
If your scripts are:
finance3 (with currencyConverter)
finance2 (with currencyConverter)
Then you will get finance2's currencyConverter when the dependency for currencyConverter is resolved.

Categories

Resources