Assuming I have a service MyService that has a property "data" that contains contents retrieved from 2 or 3 $http requests and stores it into "data". This "data" needs to be accessible or passed to a directive to process, (like a modal).
The service "MyService" contains an attribute "data" necessary for myDirective to process on first load.
// var app = angular.module...
app.service('MyService',...)
I have a separate directive "myDirective":
var myDirective = angular.module('myDirective', []);
myDirective.directive('control', ['Params', function(Params) {...
I tried to inject "MyService" by doing the following:
var myDirective = angular.module('myDirective', ['MyService']);
myDirective.directive('control', ['Params', function(Params) {...
Though it fails to instantiate saying:
error: [$injector:nomod] Module 'MyService' is not available! You either misspelled the module name or forgot to load it.
If registering a module ensure that you specify the dependencies as the second argument.
How do I properly instantiate my myDirective from myService? Is this the right approach or should I be using some controller/factory/provider?
You are treating myService as a module which it is not, it is a component of a module. You only inject modules into other modules. Once all dependent modules are injected into main module, components of all modules are directly available to other components, regardless of which module they are initially registered to.
To inject into a directive you do it the same way you are injecting Params into directive. I suspect you are needlessly creating a new module just to create a directive.
Try this way:
app.service('MyService',...);
app.directive('control', ['Params','MySerrvice', function(Params,MyService) {...
Now within the directive you have access to objects in service using MyService.propertyName
What you are trying is adding MyService service as a module to your MyDirective module which won't work.
The easy way would be to just add the directive to your app module and inject your service:
app.directive('control', ['Params', 'MyService', function(Params, MyService) {
//...
}]);
If you create extra modules for your directives and and maybe also for your services you will have to add these modules to your app module like for example (usually in app.js):
var directivesModule = angular.module('app.directives', []);
var servicesModule = angular.module('app.services', []);
var app = angular.module('app', ['app.directives', 'app.services']);
And then add your services and directives to the respective modules:
servicesModule.service('MyService',...);
directivesModule.directive('control', ['Params','MyService', function(Params, MyService) {
//...
}]);
Create one file per service/directive or a file for all services and one for all directives. Depends on the size of your app.
Related
I have this global set up for my angular App module:
var App = angular.module('App', [], function ($interpolateProvider) {
//$interpolateProvider.startSymbol('<%');
//$interpolateProvider.endSymbol('%>');
});
I include it in all pages with angular stuff.
I then have a controller loaded on a page that requires a service called 'angularFileUpload':
App.controller('FileUploadController', ['$scope', 'FileUploader', function ($scope, FileUploader) {
If i place that service inside the module array, it works fine. Is there a way of just attaching it to this controller instead... this means i do not have to load the script files for every page using this module regardless of if the controllers require the angularfileUpload service or not.
Edit: regarding the last comment
If i declare:
var App = angular.module('App');
How do i then add that service to the module?
I'm new to angular and unit-testing.
I have an application module MyApp including basic things an services, that are needed in all other modules, like service for logging loggingService
I also have an module for handling everything about map&geo-positon, called MapModule and I have an main module for application logic, called MainModule
The MainModule contains a controller, that I like to test: messageSendCtrl
The controller has some dependencies, like services from MapModule.
And: MainModule and MapModule has dependencies to the MyApp, because the loggingServiceis needed everywhere.
The code looks like that (pseudo-code):
MyApp
var MyApp = angular
.module('MyApp', ['ngRoute','MainModule','MapModule']);
MyApp.service('loggingService', function (one, two) {
[..] /*logging data somewhere for debugging application*/
});
MainModule
var MainModule = angular
.module('MainModule', []);
MainModule.controller('messageSendCtrl',
function($scope,$http, $location, locationService, loggingService) {
[...]
});
MapModule
var MapModule = angular
.module('MapModule', ['uiGmapgoogle-maps']);
MapModule.service('locationService', function (loggingService) {
[...]
What I like to test is the messageSendCtrl from the MainModule. (probably) I was able to inject the location service into the test environment. But injecting the locationService was not successful.
Probably because locationService also uses the loggingService.
Running the test results in
Error: [$injector:unpr] Unknown provider: loggingServiceProvider <- loggingService <- locationService
My test looks like that:
describe('saving a document', function() {
beforeEach(module('MainModule'));
beforeEach(module('MyApp'));
beforeEach(module('MapModule'));
describe ('messageSendCtrl', function () {
var scope,ctrl,locationService,loggingService;
beforeEach(inject(function($rootScope, $controller,_locationService_,_loggingService_) {
scope = $rootScope.$new();
ctrl = $controller('messageSendCtrl',
{$scope: scope,
locationService: _locationService_,
loggingService : _loggingService_ });
}));
it('should actual not saved', function(){
expect(scope.result).to.equal('unsaved');
});
})
});
So who can I solve the dependencies? Or is there an design problem at my application?
there are multiple things going on, let's check it one by one:
at your test, you don't need do load all your modules, load just that module, that you want to test, your ctrl is in your MainModule, so use just
beforeEach(module('MainModule'));
every module should declare its dependencies, so your MainModule declaration should look like this: var MainModule = angular.module('MainModule', ['MyApp']); because one of your controller in your MainModule dependent on a service that is in an other module (MyApp)
it is easier to test if one module do just one thing, so if you have a logging service, make a logging service module for that, and include that module where you want to use logging.
So don't make modules that is responsible for several different things, because if an other module need logging, that module will get every other service that your "godmodule" contains, and that makes difficult to test, and find bugs.
I want to automatically dependency inject an Angular built-in service into all services within an Angular module / app.
The service I want to inject is ... $exceptionHandler
I do not want $exceptionHandler to be global ... e.g. I do not want to do ...
window.$exceptionHandler = $exceptionHandler
But I also do not want to dependency inject $exceptionHandler into every service manually using ...
angular.module('myApp').factory('myService', ['$exceptionHandler', function ($exceptionHandler) {
Is it possible to automatically inject an Angular built-in service into all services within an Angular module / app ?
Many thanks
It can be made more convenient through nested modules. In the root (or global) module inject $exceptionHandler and all the other modules you create or want to use. All sub modules of the root module will have $exceptionHandler injected without further ado. You still have to name the $exceptionHandler in your controller and factory function definitions, though, so it is not possible to completely get rid of injection artefacts.
Example:
app.js
angular.module('app', ['ionic', '$exceptionHandler', 'ngCordova','app.home',
'app.impressum'])
.run(function ($ionicPlatform, $state) {
..
})
.config(function ($stateProvider, $urlRouterProvider, $provide, $exceptionHandler, $ionicConfigProvider, $compileProvider) {
$stateProvider
.state('app', {
...
})
}
);
Now the app.home-Module:
home.js
angular.module('app.home', ['app.home.controller', 'app.home.factory']);
home/controller.js
angular.module('app.home.controller', [])
.controller('homeController', function ($scope, $exceptionHandler) {
...
});
app.home.factory and the three modules for app.impressum are quite similar, so I leave that to you.
As you can see you still have to put $exceptionHandler into the function parameters of your controller, but no injection is required on the module itself, because it inherits all injections from it's parent modules app.home and app.
By using a hierarchy of modules in an AngularJS app injections can be made where due... more globally for the whole app, for module groups or only on single modules. Plus we get a very clean structure for the App's sections.
When making use of a service in a controller test do you need to initialize the service in the same way you would the controller? By this I mean do you need to pass it its own dependencies?
For example I can initialize my controller like so:
// Instantiate the controller
searchController = $controller( 'VisibilitySearchController',{
$scope: scope,
dataService: dataService
});
}));
so do I need to initialize the service according to the components it needs like $http, $resource etc as well as make spyOn calls on its functions? Or is this/should this be sufficient? (Note - my tests fail when I do the following )
// Instantiate the dataService
dataService = $injector.get( 'dataService' );
it throws this error:
* Error: [$injector:unpr] Unknown provider: $resourceProvider <- $resource <- dataService
The relevant part of the service:
myAppServices.factory('dataService', ['$http', '$resource', 'authService', 'messageService', function ($http, $resource, authService, messageService) {
}
Side note
Note - we are using Maven as our build tool and only make use of Jasmine at this point - trying to bring Karma in as our test-runner as a Maven plugin.
You must provide all the dependencies but you can mock them. This can be done by jasmine like this for example:
var mockedDataService = jasmine.createSpyObj('dataService', ['getData', 'getOtherData']);
And then you inject this mocked service to $provider:
beforeEach(function () {
module(function ($provide) {
$provide.value('dataService', mockedDataService );
});
}
Instance of this mocked service can be retrieved like this then:
inject(function (dataService) {
var dataServiceInstance = dataService;
});
This will provider mocked dataService anytime it is needed. However if you need fully functional dataService you must instantiate it but always you can mock any of its dependecies.
While you can inject dependencies into the controller manually you don't need to do it as long as you have loaded the module the service belongs to.
In your case it looks like you have not loaded the ngResource module.
If you add beforeEach(module('ngResource')) to your test (and make sure the actual script file it lives in is included in Jasmine's fileset) you should not need to inject it manually.
Note that you do not need to load angular core services like $http, but since $resource is not part of core it needs to be loaded like this.
Injecting dependencies manually is mostly useful if you want to provide a mock implementation.
EDIT: I have managed to get my unit tests running - I moved the code containing the services to a different file and a different module, made this new module a requirement for fooBar module, and then before each "it" block is called, introduced the code beforeEach(module(<new_service_module_name)). However, my application still won't run. No errors in console either. This is the only issue that remains - that when I use global scope for controllers definition, the application works, but when I use angular.module.controller - it does not.
I have a file app.js that contains the following:
'use strict';
var app = angular.module('fooBar', []);
app.config(['$routeProvider', function($routeProvider) {
$routeProvider.
when('/', {
templateUrl: 'partials/form-view.html',
controller: FormViewCtrl
}).
when('/resultDisplay', {
templateUrl: 'partials/table-view.html',
controller: TableViewCtrl
}).
otherwise({redirectTo: '/'});
}]);
app.service('searchResults', function() {
var results = {};
return {
getResults: function() {
return results;
},
setResults: function(resultData) {
results = resultData;
}
};
});
I have another file controllers.js that contains the following:
'use strict';
var app = angular.module('fooBar', []);
app.controller('FormViewCtrl', ['$scope', '$location', '$http', 'searchResults',
function ($scope, $location, $http, searchResults) {
//Controller code
}]);
searchResults is a service that I created that simply has getter and setter methods. The controller above uses the setter method, hence the service is injected into it.
As a result, my application just does not run! If I change the controller code to be global like this:
function ($scope, $location, $http, searchResults) {
//Controller code
}
then the application works!
Also, if I use the global scope, then the following unit test case works:
'use strict';
/*jasmine specs for controllers go here*/
describe('Foo Bar', function() {
describe('FormViewCtrl', function() {
var scope, ctrl;
beforeEach(module('fooBar'));
beforeEach(inject(function($rootScope, $controller) {
scope = $rootScope.$new();
ctrl = $controller('FormViewCtrl', {$scope: scope});
}));
}
//"it" blocks
}
If I revert to the module scope, I get the error -
Error: Unknown provider: searchResultsProvider <- searchResults
Thus, by using global scope my application and unit tests run but by using app.controller, they seem to break.
Another point that I have noted is that if I include the controller code in app.js instead of controllers.js, then the application and unit tests start working again. But I cannot include them in the same file - how do I get this to run in the angular scope without breaking the application and unit tests?
You don't need to go that route. You can use the modular approach, but the issue is with your second parameter.
In your app.js you have this:
var app = angular.module('fooBar', []);
Then in your controller, you have this:
var app = angular.module('fooBar', []);
What you're doing there is defining the module twice. If you're simply trying to attach to the app module, you cannot pass in the second parameter (the empty array: []), as this creates a brand new module, overwriting the first.
Here is how I do it (based on this article for architecting large AngularJS apps.
app.js:
angular.module('fooBar',['fooBar.controllers', 'fooBar.services']);
angular.module('fooBar.controllers',[]);
angular.module('fooBar.services', []);
...etc
controllers.js
angular.module('foobar.controllers') // notice the lack of second parameter
.controller('FormViewCtrl', function($scope) {
//controller stuffs
});
Or, for very large projects, the recommendation is NOT to group your top-level modules by type (directives, filters, services, controllers), but instead by features (including all of your partials... the reason for this is total modularity - you can create a new module, with the same name, new partials & code, drop it in to your project as a replacement, and it will simiply work), e.g.
app.js
angular.module('fooBar',['fooBar.formView', 'fooBar.otherView']);
angular.module('fooBar.formView',[]);
angular.module('fooBar.otherView', []);
...etc
and then in a formView folder hanging off web root, you THEN separate out your files based on type, such as:
formView.directives
formView.controllers
formView.services
formView.filters
And then, in each of those files, you open with:
angular.module('formView')
.controller('formViewCtrl', function($scope) {
angular.module('formView')
.factory('Service', function() {
etc etc
HTH
Ok - I finally figured it out. Basically, if you wish to use the module scope and not the global scope, then we need to do the following (if you have a setup like app.js and controllers.js):
In app.js, define the module scope:
var myApp = angular.module(<module_name>, [<dependencies>]);
In controllers.js, do not define myApp again - instead, use it directly like:
myApp.controller(..);
That did the trick - my application and unit tests are now working correctly!
It is best practice to have only one global variable, your app and attach all the needed module functionality to that so your app is initiated with
var app = angular.module('app',[ /* Dependencies */ ]);
in your controller.js you have initiated it again into a new variable, losing all the services and config you had attached to it before, only initiate your app variable once, doing it again is making you lose the service you attached to it
and then to add a service (Factory version)
app.factory('NewLogic',[ /* Dependencies */ , function( /* Dependencies */ ) {
return {
function1: function(){
/* function1 code */
}
}
}]);
for a controller
app.controller('NewController',[ '$scope' /* Dependencies */ , function( $scope /* Dependencies */ ) {
$scope.function1 = function(){
/* function1 code */
};
}
}]);
and for directives and config is similar too where you create your one app module and attach all the needed controllers, directives and services to it but all contained within the parent app module variable.
I have read time and time again that for javascript it is best practice to only ever have one global variable so angularjs architecture really fills that requirement nicely,
Oh and the array wrapper for dependencies is not actually needed but will create a mess of global variables and break app completely if you want to minify your JS so good idea to always stick to the best practice and not do work arounds to get thing to work
In my case, I've defined a new provider, say, xyz
angular.module('test')
.provider('xyz', function () {
....
});
When you were to config the above provider, you've inject it with 'Provider' string appended.
Ex:
angular.module('App', ['test'])
.config(function (xyzProvider) {
// do something with xyzProvider....
});
If you inject the above provider without the 'Provider' string, you'll get the similar error in OP.