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
Related
I'm very new to Javascript and Protractor. Still trying to get my head around simple syntax so forgive me if I'm way off base here.
So our angular app, has a module with a factory that generates toast messages. I'd like to disable all toast messages during my E2E testing. We have a function within the factory to disable toasts. Here's some simplified code.
//the module
var module = angular.module('toast',[]);
//the factory
module.factory('tf',[function tf(){
//factory code
//the function within the module's factory
moduleFactory.enable = function(enable){
isEnabled = enable;
};
}]);
My question is, can I access that function in protractor to turn that to false?
I've been searching around and it seems that mocking is how to do it. Something similar to how you disable angular animations.
// Disable animations so e2e tests run more quickly
var disableNgAnimate = function() {
angular.module('disableNgAnimate', []).run(['$animate', function($animate) {
$animate.enabled(false);
}]);
};
browser.addMockModule('disableNgAnimate', disableNgAnimate);
However, I'm struggling with the syntax on accessing the factory's function within the module...Any help would be appreciated.
I believe I've found the solution for anyone else that may have a similar issue.
Using the executeScript function of protractor.
browser.executeScript(function()
{
return angular.element(document).injector().get('toastFactory').enableToasts(false);
});
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.
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.
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
I am trying to work things out with angularjs, dependency injections, factories and signalR hubs, but I get the error Unknown provider: personHubProvider <- personHub and I cannot figure out what is causing it.
My code is like this:
First I declare the module:
var ucp = angular.module('UCP', []);
which is used in the html tag by ng-app="UCP".
Then I do some configuration on the ucp module for the hub settings and creating a signalRHub factory to get the hubs without typing much of the same code:
ucp.config(function($routeProvider) {
//Declaring routes, removed this part of code cause I think it hasn't to do
//with the problem I got
$.support.cors = true;
$.connection.hub.url = config.signalR.connectionURL;
$.connection.hub.logging = config.signalR.logging;
$.connection.hub.start();
}).factory('signalRHub', [function() {
return {
person: $.connection.Persons.server
};
}]);
Then, I create another factory, that takes care of getting the data from the server:
ucp.factory('personHub', ['signalRHub', function(signalRHub) {
return {
get: function(onsuccess, onerror) {
signalRHub.person.get()
.done(function(persons) {
onsuccess(persons);
})
.fail(function(error) {
onerror(error);
});
}
}
}]);
and this factory I inject in my controller so I can execute the call to get the data from the server and put it in the scope which provides to show the data in the browser:
ucp.controller('personController', ['$scope', 'personHub', function($scope, personHub){
var self = this;
$scope.init = function() {
personHub.get(self.ongetsuccess, self.ongeterror);
}
self.ongetsuccess = function(persons) {
$scope.persons = persons;
};
self.ongeterror = function(error) {
};
}]);
When I open the webpage I get the error I mentioned before: Error: Unknown provider: personHubProvider <- personHub.
I think something goes wrong with creating the personHub factory service, which on his turn causes an Dependency Injection error for the controller. My question is, what is causing the error, am I doing something wrong with creating the personHub factory and if so, what am I doing wrong?
As I had stated in the comments I know this error occurs because the injected $provider isn't defined therefore it's reporting that there is nothing to provide said $provider. A $provider might be a factory, service, controller, or value (there's probably others I'm forgetting) and must be defined before it is referenced for an injection.
More on providers and injection here: https://github.com/angular/angular.js/wiki/Understanding-Dependency-Injection
The way I've been dealing with organizing code is by encapsulating most of the parts necessary for a given view into one js file. In that JS file I start off with a services section where I define a new module like:
angular.module("loginModule.services",[/*dependencies*/]).service("loginService", [/*dependencies*/function(){ return {get:function(){return "what up!"}};}]);
Then lower in the file I define my controllers like
angular.module("loginModule.controllers",[/*dependencies*/]).controller("LoginCtrl" ,["$scope", function($scope) { /* code here* /}]);
then at the bottom of the file
angular.module("loginModule", ["loginModule.services", "loginModule.controllers"]);
And finally in my mainApp.js (with the main ng-app module)
angular.module("mainApp", ["loginModule"]);
I've defined my script blocks in the head at the moment.
<script type="text/javascript" src="components/loginModule.js"></script>
<script type="text/javascript" src="mainApp.js"></script>
Also to note my loginModule actually depends on other services for which the javascript files come afterward. This doesn't seem to be an issue since the mainApp.js is being deferred until the end.
I just checked some of this in Chrome and it appears the JS files from my computer do load in order and as pairs (only two per domain at a time then when a response comes back the next request is fired). I also tried moving the script blocks from the head to the bottom of the HTML and I can't tell any difference (my laptop has an SSD and mostly local files so probably more appreciable in a production scenario).