Angular Controller Testing: check dependency injection is done right - javascript

Sometimes, often when refactoring a Controller - or also a Factory -, I end up passing my Controller tests but when my app is up and running it keeps crashing at some point because I forgot to add/update dependency injection.
With this I mean, let's say I have the following Controller where I used to have an oldDependency but due to refactoring I'm using a newDependency instead. I update MyCtrl.$inject with the new changes but I forget to update the dependencies passed to the MyCtrl function:
angular
.module('my-module')
.controller('MyCtrl', MyCtrl);
MyCtrl.$inject = [
'firstDependency',
'secondDependency',
'newDependency' // this has been only updated here
];
function MyCtrl(firstDependency, secondDependency, oldDependency) {
var vm = this;
// My Controller's code
// etc...
function someFunc(x) {
// here I use newDependency
return newDependency.doSomething(x);
}
}
So what happens then? I go and update MyCtrl tests, where I actually remember to update the dependency object passed to $controller():
// MyCtrl testing code
var MyCtrl = $controller('VolunteerResignCtrl', {
firstDependency: firstDependency,
secondDependency: secondDependency,
newDependency: newDependency
});
Because of this, all MyCtrl tests keep passing, so I think that nothing broke. But it actually did.
Could anyone tell me if this can be tested somehow and avoid my app failing for this reason in the future?

This seems like an issue with JS scoping rather than Angulars dependency injection. Try wrapping your test cases and the functions that you use for them in an IIFE as it they're not, JS will go up the scope until it can find a variable called newDependency which is probably why your tests are running but it's crashing in your app.
As you can see from the examples below
Without an IIFE
var myApp = angular.module('myApp', []);
myApp.factory('firstDependency', function() {
return {}
}());
myApp.factory('secondDependency', function() {
return {}
}());
myApp.factory('newDependency', function() {
return {}
}())
myApp.controller('MyCtrl', MyCtrl);
myApp.controller('TestCtrl', TestCtrl);
MyCtrl.$inject = [
'firstDependency',
'secondDependency',
'newDependency'
];
function MyCtrl(firstDependency, secondDependency, oldDependency) {
var vm = this;
vm.someFunc = function(x) {
// Will work fine as newDependency is defined in a higher scope :c
return newDependency.doSomething(x);
}
}
var firstDependency = function() {
return {}
}();
var secondDependency = function() {
return {}
}();
var newDependency = function() {
return {
doSomething: function(x) {
return x;
}
}
}()
function TestCtrl($scope, $controller) {
var test = $controller('MyCtrl', {
firstDependency: firstDependency,
secondDependency: secondDependency,
newDependency: newDependency
});
$scope.someFunc = test.someFunc("you're inside the new dependency");
}
<script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.2.23/angular.min.js"></script>
<div ng-app="myApp">
<div ng-controller="TestCtrl">
{{someFunc}}!
</div>
</div>
With an IIFE
var myApp = angular.module('myApp', []);
myApp.factory('firstDependency', function() {
return {}
}());
myApp.factory('secondDependency', function() {
return {}
}());
myApp.factory('newDependency', function() {
return {}
}())
myApp.controller('MyCtrl', MyCtrl);
MyCtrl.$inject = [
'firstDependency',
'secondDependency',
'newDependency'
];
function MyCtrl(firstDependency, secondDependency, oldDependency) {
var vm = this;
vm.someFunc = function(x) {
//Will throw an error, but this time it's a good error because that's what you want!
return newDependency.doSomething(x);
}
}
(function(myApp) {
var firstDependency = function() {
return {}
}();
var secondDependency = function() {
return {}
}();
var newDependency = function() {
return {
doSomething: function(x) {
return x;
}
}
}()
myApp.controller('TestCtrl', TestCtrl);
function TestCtrl($scope, $controller) {
var test = $controller('MyCtrl', {
firstDependency: firstDependency,
secondDependency: secondDependency,
newDependency: newDependency
});
$scope.someFunc = test.someFunc("you're inside the new dependency");
}
})(myApp)
<script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.2.23/angular.min.js"></script>
<div ng-app="myApp">
<div ng-controller="TestCtrl">
{{someFunc}}!
</div>
</div>

Name of variables are not important from js-point of view.
Following functions are SAME:
function f(SomeService) {
SomeService.test();
}
function f(NewService) {
NewService.test();
}
function f(a) {
a.test();
}
Thats the reason why you need $inject - cause you pass string their.
After you change name in $inject - you may change it in function, but you also may not change it. Doesnt matter at all.

Related

Run same piece of code when injecting an angular service

I want to run the same line of code to each function a specific service is injected. For instance:
app.service('sameInitService', function ($rootScope) {
this.init = function (scope) {
scope.foo = $rootScope.foo;
}
});
app.controller('oneController', function ($scope, sameInitService) {
sameInitService.init($scope); // <- This line of code
});
app.controller('twoController', function ($scope, sameInitService) {
sameInitService.init($scope); // <- Is the same
});
Is there an angular-ish way to avoid having to write the same line of code when this service is injected?
It can be done by decorating the $controller service but in this case all your controllers will be initialized this way.
angular.module('App', [])
.config(['$provide', function($provide) {
$provide.decorator('$controller', [
'$delegate', 'sameInitService',
function controllerDecorator($delegate, sameInitService) {
return function(constructor, locals) {
sameInitService.init(locals.$scope);
return $delegate(constructor, locals, true);
}
}
]);
}])
See jsfiddle here.
I want to extend on #janusz 's answer and present the code that I actually ended up using, which takes care of the "apply behavior only to controllers with injected dependency":
angular.module('App').decorator('$controller', function ($delegate, sameInitService) {
return function (constructor, locals) {
var controller = $delegate.apply(null, arguments);
return angular.extend(function () {
var controllerInstance = controller()
var DIList = controllerInstance .__proto__.constructor.$inject;
console.log(DIList);
if (DIList.indexOf('sameInitService') !== -1) {
sameInitService.init(locals.$scope);
}
return controllerInstance;
}, controller);
};
});
Here is a modified fiddle

Using Angular Controllers with prototype

my question is really simple.
It is possible use Angular Controllers with prototype?
'use strict';
var EventController = function(scope, EventModel) {
this.scope = scope;
this.EventModel = EventModel;
};
EventController.prototype = {
create: function() {
this.scope.create = function() {
this.EventModel.Model.insert()
.then(function(result) {
});
};
},
retrieve: function() {
var that = this;
this.EventModel.Model.find()
.then(function(result) {
that.scope.events = result;
});
},
retrieveOne: function(id) {
this.EventModel.Model.findOne(id)
.then(function(result) {
console.log(result);
});
},
update: function() {
this.EventModel.Model.update()
.then(function(result) {
});
},
delete: function() {
this.EventModel.Model.remove()
.then(function(result) {
});
}
};
module.exports = function(adminApp) {
adminApp
.controller('EventController', ['$scope', 'EventModel', function(scope, EventModel) {
return new EventController(scope, EventModel);
}]);
};
I'm using Browserify, that's why i have this module.exports in the final.
I would love to use controllers that way and get the methods as if it were the name of the objects in the prototype.
There is any way that i can do that?
You absolutely can. Here's a demo I've produced using similar code to yours.
The magic of this is in the "Controller as" syntax, which was introduced in Angular 1.2.0.
There are three buttons in the demo. One uses the prototype methods on the controller itself, as you asked for, and the other two illustrate that you can continue to use the controller as you're probably used to, by using the scope entirely in the markup, and by supplying scope methods in the controller.
https://jsfiddle.net/scarl3tt/3copx1rk/1/
Javascript
'use strict';
var TestController = function(scope) {
this.scope = scope;
};
TestController.prototype = {
doAThing: function() {
alert('This is a thing');
}
};
angular.module('testApp', [])
.controller('TestController', ['$scope', function(scope) {
if(typeof(scope.extension) === 'undefined')
scope.extension = function() {
scope.j *= 2;
};
return new TestController(scope);
}]);
Markup
<div ng-app="testApp" ng-controller="TestController as ctrl" ng-init="i = 0; j = 1;">
<h1>Controller as:</h1>
<button ng-click="ctrl.doAThing()">Do a thing!</button>
<h1>Scope:</h1>
<button ng-click="i=i+1">i == {{i}}</button>
<h1>Extension method using scope</h1>
<button ng-click="extension()">j == {{j}}</button>
</div>

How can i access scope in angular service

I have two controllers
app.controller('TestCtrl1', ['$scope', function ($scope) {
$scope.save = function () {
console.log("TestCtrl1 - myMethod");
}
}]);
app.controller('TestCtrl2', ['$scope', function ($scope) {
$scope.var1 = 'test1'
$scope.save = function () {
console.log("TestCtrl1 - myMethod");
}
}]);
Then i have two services
.service('Service1', function($q) {
return {
save: function(obj) {
}
}
})
.service('Service2', function($q) {
return {
save: function(obj) {
}
}
})
For my 60% of stuff i just call save on ctrl1 which then called service save method
Now There are cases where before saving i need to do some stuff like chnaging some object parameters different than genral case there i check e,g
if(model == 'User'){
//Here i do this (sample of code)
var service = $injector.get('Service2');
service.save()
Now my problem is in Service 2 i need access to var1. How can i do that
Use the service(s) itself to share the variable as part of the service object as well as methods of each service
.service('Service2', function($q) {
var self = this;
this.var1 = 'test1';
this.save = function(obj) {
}
});
app.controller('TestCtrl2', ['$scope','Service1','Service2', function ($scope, Service1, Service2, ) {
// bind scope variable to service property
$scope.var1 = Service2.var1;
// add a service method to scope
$scope.save = Service1.save;
// now call that service method
$scope.save( $scope.var1 );
}]);
You can also inject a service into another service if needed
injecting services into other services (one possible method) :
html:
<div id="div1" ng-app="myApp" ng-controller="MyCtrl">
<!--to confirm that the services are working-->
<p>service three: {{serviceThree}}</p>
</div>
js:
angular.module('myApp',[])
.service('s1', function() {
this.value = 3;
})
.service('s2', function() {
this.value = 10;
})
.service('s3', function(s1,s2) { //other services as dependencies
this.value = s1.value+s2.value; //13
})
.controller('MyCtrl', function($scope, $injector) { //$injector is a dependency
$scope.serviceThree = $injector.get('s3').value; //using the injector
});
here's the fiddle: https://jsfiddle.net/ueo9ck8r/

using angular.copy to extend a service

I have made baseService in angular as following.
app.service('baseService', ['$http', function($http) {
'use strict';
this.service = "";
this.load = function(){
// implement load
}
}]);
Now i want other services to extend this service as the implementation of load method will be same for all the services. My derived service will look as follow.
app.service('jobsService', ['baseService', function( baseService ){
'use strict';
angular.copy(baseService, this);
this.service = 'jobs';
}]);
Here I am using angular.copy(baseService, this); to extend the baseService. Is this a right approach to inherit a service?
Yes... I'd say so.
You're approach is fine and I'd agree that this an appropriate solution for this. More of a best practice question rather than specifics. Here is a fully working example with both the service and factory patterns Angular provides for different development preference
fiddle
angular.module('app', []);
angular.module('app').factory('BaseFactory', [ function() {
var load = function() {
console.log('loading factory base');
}
return { 'load': load }
}]);
angular.module('app').factory('ChildFactory', ['BaseFactory', function (BaseService) {
var child = angular.copy(BaseService);
child.childLoad = function () {
console.log('loading factory child');
};
return child;
}]);
angular.module('app').service('BaseService', [function() {
this.load = function() {
console.log('loading service base');
}
}]);
angular.module('app').service('ChildService', ['BaseService', function(BaseService) {
angular.copy(BaseService, this);
this.childLoad = function() {
console.log('loading service child');
}
}]);
angular.module('app').controller('ctrl', ['$scope', 'ChildFactory', 'ChildService', function($scope, ChildFactory, ChildService) {
ChildService.load(); // 'loading service base'
ChildService.childLoad(); // 'loading service child'
ChildFactory.load(); // 'loading factory base'
ChildFactory.childLoad(); // 'loading factory child'
}]);
A simpler and fairly straightforward option will be to use John Resig's Simple JavaScript Inheritance class.
This gives you a more object oriented approach to inheriting/extending classes plus extra functionalities like constructor calls, function overrides etc.
Using your example:
'use strict';
angular.module('app').factory('BaseService', function ($http) {
var service = Class.extend({ //Class from John Resig's code
init: function (params) {
this.params = params;
},
load: function (params) {
return;
},
return service;
});
Then to extend/inherit:
angular.module('app').factory('JobService', function (BaseService) {
var service = BaseService.extend({
init: function (params) {
this._super(params);
}
});
return new service;
});
var BaseService = (function () {
var privateVar = 0;
return {
someAwesomeStuff: function () {
if (privateVar === 42) {
alert('You reached the answer!');
}
privateVar += 1;
};
};
}());
And create child service like this:
var ChildService = Object.create(BaseService);
ChildService.someMoreAwesomeStuff = function () {
//body...
};
module.factory('ChildService', function () {
return ChildService;
});
and you can use this ina controller like this:
function MyCtrl(ChildService) {
ChildService.someAwesomeStuff();
}
reference
Here is your solution (extended2Service), and a solution that i usually use (extendedService).
Personally i prefer my solution because i don't made a new istance of that object.
angular.module('app',[])
.controller('mainCtrl', ['$scope', 'baseService', 'extendedService', 'extended2Service', function($scope, baseService, extendedService, extended2Service ){
$scope.test = 8;
$scope.resultBase = baseService.addOne($scope.test);
$scope.resultExtended = extendedService.sum($scope.test, 10);
$scope.result2Extended = extended2Service.sum($scope.test, 10);
console.log(extendedService);
console.log(extended2Service);
}])
.factory('baseService', function(){
var baseService = {};
var _addOne = function(n) {
return n+1;
};
baseService.addOne = _addOne;
return baseService;
})
.factory('extendedService', ['baseService', function(baseService){
var extendedService = {};
var _sum = function(a, b){
for (var i = 0; i < b; i++) {
a = baseService.addOne(a);
}
return a;
};
extendedService.addOne = baseService.addOne;
extendedService.sum = _sum;
return extendedService;
}])
.factory('extended2Service', ['baseService', function(baseService){
var extended2Service = angular.copy(baseService);
var _sum = function(a, b){
for (var i = 0; i < b; i++) {
a = baseService.addOne(a);
}
return a;
};
extended2Service.sum = _sum;
return extended2Service;
}]);
<script src="//ajax.googleapis.com/ajax/libs/angularjs/1.3.2/angular.min.js"></script>
<meta charset="utf-8">
<title>JS Bin</title>
</head>
<body>
<div ng-app="app" ng-controller="mainCtrl">
Value: {{test}} <br>
Base: {{resultBase}} <br>
resultExtended : {{resultExtended}} <br>
result2Extended: {{result2Extended}}
<div>

Subclass factory in AngularJS with member variables

I have tens of AngularJS Factories which have a lot in common. So I'm trying to make a base class and subclass it.
But I've noticed that the subclasses are sharing member variables of the base class.
I've made an example on http://jsbin.com/doxemoza/2/edit, the code is also posted here:
HTML:
<!DOCTYPE html>
<html>
<head>
<script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.2.3/angular.min.js"></script>
<meta charset=utf-8 />
<title>JS Bin</title>
</head>
<body ng-app="demo" ng-controller="MainCtrl">
<p>ChildService: {{value1}}</p>
<p>AnotherChildService : {{value2}}</p>
</body>
</html>
JavaScript:
angular.module('demo', []);
var demo = angular.module('demo').controller('MainCtrl', function ($scope, ChildService, AnotherChildService) {
$scope.value1 = ChildService.getPrivateVar();
$scope.value2 = AnotherChildService.getPrivateVar();
});
var Base = function () {
var Service = {};
Service.privateVar = 0;
Service.setPrivateVar = function (value) {
Service.privateVar = value;
}
Service.getPrivateVar = function () {
return Service.privateVar;
}
return Service;
};
demo.factory('BaseService', Base)
demo.factory('ChildService', function (BaseService) {
var ChildService = Object.create(BaseService);
ChildService.setPrivateVar(1);
return ChildService;
});
demo.factory('AnotherChildService', function (BaseService) {
var AnotherChildService = Object.create(BaseService);
AnotherChildService.setPrivateVar(2);
return AnotherChildService;
});
My expected output is:
ChildService: 1
AnotherChildService : 2
But instead I get:
ChildService: 2
AnotherChildService : 2
I think ChildService and AnotherChildService are sharing the same privateVar, so I got the same value for them.
How should I change the code to make them use different instance of privateVar?
Thanks
I had that same problem, and was solved when a declared my BaseService this way:
demo = angular.module('demo', []);
demo.factory('BaseService', function(){
return {
privateVar: 0,
setPrivateVar: function (value) {
this.privateVar = value;
},
getPrivateVar: function () {
return this.privateVar;
}
}
});
My "child" services are like yours. Everything works very fine.
I use something like this:
angular.module('app')
.controller('ParentController', ['$scope', 'dataService', function ($scope, dataService) {
//controller logic here
}])
.controller('ChildController', ['$scope', '$controller', 'SomeDataService', function ($scope, $controller, SomeDataService) {
//extend your parentcontroller
$scope.init = function () {
//do something
}
angular.extend(this, $controller('ParentController', {
$scope: $scope,
dataService: SomeDataService
}));
}])
.factory('BaseDataService', ['logger', function (logger) {
var privateArray = [];
return {
publicFunction: function(){ return privateArray; },
publicVar: "some var"
}
}])
.factory('SomeDataService', ['BaseDataService', function (dataService) {
var service = angular.extend({}, dataService);
service.privateFunction = function () {
//some code
}
service.privateVar= "some value";
return service;
}]);
I combined all of them in this example.
Hope this helps.
Edit: this uses the mixin pattern described in this post

Categories