How to solve controller multi arument form in Angular? - angularjs

I have about 10 controllers and 20 services.
Each controller uses at least 5 the same services and modules. For example:
app.controller('GroupsCtrl', function(
$rootScope,
$scope,
service1,
service2,
service3,
service4,
service5,
service6,
...
service20
)
{ /**/}
It seems pretty ugly and messy.
Is Angular provide any solution that will solve multi argument problem? For example new form:
app.controller('GroupsCtrl', function(
$rootScope,
$scope,
SomeObject, // goes here that contains other absent services
service6,
...
service20
)
{ /**/}
And SomeObject will install absent services:
SomeObject:[
service1,
service2,
service3,
service4,
service5,
]
So after that all my 10 controllers can inherit SomeObject instead to write full list each time.
Hope it was clear,
Thanks,

Yes you can. Create a factory which returns the services you want and use that factory in your controllers to access the services.
Ex.
var myapp = angular.module('myapp', []);
myapp.service('service1', function() {
this.test = function(){
console.log("Inside service1 -->test");
//alert("Inside service1 -->test")
}
});
myapp.service('service2', function() {
this.test = function(){
console.log("Inside service2 -->test");
//alert("Inside service2 -->test");
}
});
myapp.factory('servicesFactory', function(service1,service2) {
return {
service1 : service1,
service2 : service2
};
});
myapp.controller('Ctrl', function ($scope,servicesFactory) {
servicesFactory.service1.test();
servicesFactory.service2.test();
});

One simple solution can be using the $inject property annotation to inject the array of service names and referr the services using the arguments array as below.
//Common services array
var commonServices = ['$scope','$http'];
function initiateCtl(services){
this.scope = services[0]; this.http = services[1];
}
//Controller 1
var sampleCtrl = function(){
initiateCtl(arguments);
var $window = arguments[2];
console.log(scope);
};
sampleCtrl['$inject'] = commonServices.concat(['$window']);
app.controller('sampleCtrl', sampleCtrl);
//Controller 2
var sampleCtrl2 = function(){
initiateCtl(arguments);
var $location= arguments[2];
console.log(scope);
};
sampleCtrl2['$inject'] = commonServices.concat(['$location']);
app.controller('sampleCtrl2', sampleCtrl2 );

Related

In AngularJS Factory or Service, do we need to set a "self" or temporary local variable to remember "myself" or "this"?

Short question is: do we need to use var self = this; in a AngularJS Factory and Service?
I have seen AngularJS code for factory or service that uses a var self = this; to remember itself (the service object), and then inside of the functions of the service object, use self to refer to itself. (kind of like how we are afraid we will lose what this is, so we set self = this to use it later, by the principle of closure.
However, I have found that we can safely use this. Why? Because we get back a singleton service object, and when we invoke myService.getNumber(), the this is bound to the service object myService, so it does work. Example:
If it is a service:
https://jsfiddle.net/yx2s3e72/
angular.module("myApp", [])
.service("myService", function() {
console.log("Entering factory");
this.s = "hello";
this.getLength = function() {
return this.s.length;
};
})
.controller("myController", function(myService) {
console.log("Entering controller");
var vm = this;
vm.s = myService.s;
vm.length = myService.getLength();
});
or if it is a factory:
https://jsfiddle.net/935qmy44/
angular.module("myApp", [])
.factory("myService", function() {
console.log("Entering factory");
return {
s: "hello",
getLength: function() {
return this.s.length;
}
};
})
// controller code not repeated here...
It works too. So is it true that we really don't need to set a var self = this; in a custom AngularJS factory or service?
You may lose the reference to this, therefore the reference is saved in the self var.
angular.module('myApp', [])
.run(function (MyService) {
MyService.method();
})
.service('MyService', function ($timeout) {
this.key = 'value';
this.method = function () {
console.log(this) // service
$timeout(function () {
console.log(this); // window because callback is called from the window
}, 0);
}
});
angular.bootstrap(document.querySelector('#app'), ['myApp']);
See JSFiddle

Angular calling shared service data update in controller

I am trying to write some very primitive angular code with 2 controllers and 1 service.
So when I call shared service from controller 1 and update data, I want to use same in my controller 2 $scope so that controller 2 $scope value can reflect on my DOM.
App.controller('oneCtrl', function($scope, $uibModal, $log, sharedProperties) {
// Call a new DOM element to so that ModalInstanceCtrl will be called
// Once controller 2 finishes, I want to update a $scope variable here
// $scope.projectList = getProjectList();
});
App.controller('ModalInstanceCtrl', function ($scope, $uibModalInstance, sharedProperties) {
// This is a new modal which uses sharedProperties
// Update setProjectList() in service
});
App.service('sharedProperties', function() {
var projectList = new Array();
return {
getProjectList: function() {
return projectList;
},
setProjectList: function(value) {
projectList.push(value);
},
}
});
Once controller 2 calls setProjectList(). I want to auto update $scope value in controller 1 using getProjectList()
Please let me know how I can do that? Also do let me know if any further details needed on same.
A service in angular is a singleton so if you change data on the service it will be reflected whenever you call that service.
var app = angular.module('plunker', []);
app.controller('FirstCtrl', function($scope, userData) {
$scope.favoriteBook = userData.favoriteBook;
$scope.getFavoriteBook = function(){
$scope.favoriteBook = userData.favoriteBook;
}
});
app.controller('SecondCtrl', function($scope, userData) {
$scope.changeBook = function(){
userData.favoriteBook = 'The Hobbyt';
}
});
app.factory('userData', function(){
var favoriteBook = 'Harry Potter';
return{
favoriteBook : favoriteBook
}
})
Here you got a service that exposes an object, you can change the value of that object in the second controller and see it reflected in the first controller. Call changeBook(), and then getFavoriteBook()
This is the plunker:
the plunker

angular directive with dependencies testing

I am trying to be a good developer & write some tests to cover a directive I have. The directive has a service injected in which makes a call to a webApi endpoint.
When I run the test (which at minute expects 1 to equal 2 so I can prove test is actually running!!) I get an error that an unexpected request GET has been made to my real endpoint even though I thought I had mocked/stubbed out the service so test would execute. My test looks something like the below:
I thought that by calling $provide.service with the name of my service and then mocking the method "getUserHoldings" then this would automatically be injected at test time, have I missed a trick here? The path of the endpoint the unexpected request is contained in the actual getUserHoldings method on the concrete service.
Thanks for any help offered as driving me potty!!!
describe('directive: spPlanResults', function () {
var scope;
var directiveBeingTested = '<sp-plan-results></sp-plan-results>';
var element;
beforeEach (module('pages.plans'));
beforeEach (inject(function ($rootScope,
$compile,
currencyFormatService,
_,
moment,
plansModel,
appConfig,
$timeout,
$q,
$provide) {
scope = $rootScope.$new();
$provide.service('plansService', function () {
return {
getUserHoldings: function() {
var deferred = $q.defer();
return deferred.resolve([
{
class: 'Class1',
classId: 2,
award: 'Award1',
awardId : 2
}]);
}
};
});
element = $compile(directiveBeingTested)(scope);
scope.$digest();
});
it ('should be there', inject(function() {
expect(1).equals(2);
}));
});
Referencing - http://www.mikeobrien.net/blog/overriding-dependencies-in-angular-tests/ - it would work if you did your '$provide' configuration in the 'module's context i.e. do something like -
describe('directive: spPlanResults', function () {
var scope;
var directiveBeingTested = '<sp-plan-results></sp-plan-results>';
var element;
beforeEach(module('pages.plans', function($provide) {
$provide.value('plansService', function() {
return {
getUserHoldings: function() {
var deferred = $q.defer();
return deferred.resolve([{
class: 'Class1',
classId: 2,
award: 'Award1',
awardId: 2
}]);
}
};
});
}));
beforeEach(inject(function($rootScope, $compile, currencyFormatService, _, moment, plansModel, appConfig, $timeout, $q) {
scope = $rootScope.$new();
element = $compile(directiveBeingTested)(scope);
scope.$digest();
});
it('should be there', inject(function() {
expect(1).equals(2);
})); });

AngularJS unit test controller with only private functions

I have following controller that has only private functions. I am struggling to test this controller. Shall I test if it is doing $emit, since the ImageService has been tested? How do I test $emit in this case? Or shall I test if it is calling the ImageService.fetchImageStacks method? In this case, how do I trigger init function?
(function (angular, global, undefined) {
'use strict';
var ImageController = {};
ImageController.$inject = [
'$rootScope',
'$log',
'ImageService'
];
ImageController = function (
$rootScope,
$log,
ImageService
) {
var getImageStacks = function() {
ImageService
.fetchImageStacks()
.success(function (result) {
ImageService.setImageStacks(result);
$rootScope.$emit('rootScope:imageStacksUpdated', result);
})
.error(function (){
$log.error('Failed to get imageStackInfo file.');
});
};
var init = function () {
getImageStacks();
};
init();
return {
getImageStacks: getImageStacks
};
}
angular.module('myApp')
.controller('ImageController', ImageController);
})(angular, this);
You shouldn't be testing private/internal methods that are not available to the outside world(imho).
Some resources on the subject (for & against):
http://www.peterprovost.org/blog/2012/05/31/my-take-on-unit-testing-private-methods/
http://www.quora.com/Should-you-unit-test-private-methods-on-a-class
http://henrikwarne.com/2014/02/09/unit-testing-private-methods/
Should Private/Protected methods be under unit test?
Should I test private methods or only public ones?
With that said, you are exposing getImageStacks on the controller - so it isn't a private method. If you were to log out the result of instantiating the controller in your test suite, you should see something of the sort:
{ getImageStacks: function }
(init() in your case, is just an alias for getImageStacks (aka there is no need for the init method - you could just call getImageStacks and be done with it)).
Anyway, to write some tests;
First of, you should stub out the ImageService as we are not interested in the internal implementation of said service, we are only ever interested in the communication going from the controller to the service. A great library for stubbing/mocking/spying is sinonjs - get it, you won't regret it.
In the beforeEach I would suggest you do something like this:
// Stub out the ImageService
var ImageService = {
fetchImageStacks: sinon.stub(),
setImageStacks: sinon.stub()
};
var $scope, instantiateController;
beforeEach(function () {
// Override the ImageService residing in 'your_module_name'
module('your_module_name', function ($provide) {
$provide.value('ImageService', ImageService);
});
// Setup a method for instantiating your controller on a per-spec basis.
instantiateController = inject(function ($rootScope, $controller, $injector) {
ctrl = $controller('ImageController', {
$scope: $rootScope.$new(),
// Inject the stubbed out ImageService.
ImageService: $injector.get('ImageService')
});
});
});
Now you have a stubbed out ImageService to test calls against, and a method to instantiate your controller with the dependencies passed into it.
Some example specs you can run;
it('calls the ImageService.fetchImageStacks method on init', function () {
instantiateController();
expect(ImageService.fetchImageStacks).to.have.been.calledOnce;
});
it('calls the ImageService.setImageStacks on success', inject(function ($q, $timeout) {
ImageService.getImageStacks.returns($q.when('value'));
instantiateController();
$timeout.flush();
expect(ImageService.setImageStacks).to.have.been.calledOnce.and.calledWith('value');
}));
I hope that will suffice and answer your questions on;
If/when you should/shouldn't test internal implementation.
How to test the initialisation of the controller.
How to test methods of an injected service.

Data service created by angularjs factory doesn't get dependencies

I am trying to wire up a simple data service to retrieve data from the server for http calls. I am using TypeScript to write the code. For some reason I can't get the service to see its dependencies.
Here is the service the way it is generated by Typescript
var app = angular.module('app',[]);
app.constant('baseUrl', 'http://localhost:63342');
//This follows the pattern created by Typescript
var myService = function(){
function myService($http, baseUrl){
this.$http = $http;
this.baseUrl = baseUrl;
this.http = typeof this.$http;
this.url = typeof this.baseUrl;
}
myService.$inject = ['$http', 'baseUrl'];
return myService
}
app.factory('myService', [
'$http', 'baseUrl',
myService
]);
app.controller('myCtrl',
['$scope', 'myService',
function($scope, myService){
$scope.httpType = myService.http;
$scope.urlType = myService.url}]
);
When I run the code locally the p tags get the ng-bind attributes set on them. But, of course, they have nothing in them. When I break on the $scope assignments, myService is available and it has the $inject variable, but none of the other 4 variables. The available examples for Typescript and angular are pretty thin. There must be something really basic I am doing wrong.
Here is a fiddle with the code. I don't know why the fiddle doesn't transclude the scope variables.
The issue is in :
var myService = function(){
function myService($http, baseUrl){
this.$http = $http;
this.baseUrl = baseUrl;
this.http = typeof this.$http;
this.url = typeof this.baseUrl;
}
myService.$inject = ['$http', 'baseUrl'];
return myService
}
app.factory('myService', [
'$http', 'baseUrl',
myService
]);
Angular will call myService with arguments $http,baseUrl which myService does not accept. So you need to do :
var myService = function($http, baseUrl){
this.$http = $http;
this.baseUrl = baseUrl;
this.http = typeof this.$http;
this.url = typeof this.baseUrl;
}
myService.$inject = ['$http', 'baseUrl'];
Alternatively if you want to use TypeScript classes use the same pattern I recommend for Controllers : http://www.youtube.com/watch?v=WdtVn_8K17E&hd=1 and use service instead of factory
I was able to get it to work following a module pattern to expose your service's properties, as well as using the inline notation to declare the service. Check out the plunker here: http://plnkr.co/edit/tVajIshcCegIwywGWL9Y?p=preview. Code pasted below for the script:
var app = angular.module('app',[]);
app.constant('baseUrl', 'http://developer.echonest.com/api/v4/artist/news?api_key=FILDTEOIK2HBORODV&id=7digital-US:artist:304&format=json');
app.factory('myService', ['$http', 'baseUrl', function($http,baseUrl){
var http = typeof $http;
var url = typeof baseUrl;
return {
http: http,
url : url
};
}]);
app.controller('myCtrl',
['$scope', 'myService',
function($scope, myService){
$scope.httpType = myService.http;
$scope.urlType = myService.url;
}]
);

Resources