How to inject variable into controller - javascript

I am trying to get started with angular.js but I can't figure out how to inject a simple variable into my controller before testing.
This is my controller:
angular.module("app").controller("SimpleController", SimpleController);
function SimpleController() {
var vm = this;
vm.myVar = 1;
vm.getMyVar = function() {
return vm.myVar;
};
}
My test looks like the following:
describe("SimpleController", function() {
var vm;
beforeEach(module('app'));
beforeEach(inject(function($controller, $rootScope) {
vm = $controller('SimpleController', {
$scope: $rootScope.$new(),
myVar: 2
});
}));
it('myVar should be 2 not 1', function() {
expect(vm.getMyVar()).toEqual(2);
});
});
I did a lot of google searching but this is really confusing because many people do use $scope in controller and not thisas I do. But I guess the injection of variables should work with this too?

You may want to try writing your controller this way. This will make $scope accessible.
(function () {
angular.module("app").controller("SimpleController", SimpleController);
SimpleController.$inject = ['$scope'];
function SimpleController($scope) {
$scope.somevar = "something";
}
})();
I'm sure you've probably came across the documentation, but here is a link to the docs which contains the basics and should at least get you going in the right direction.
Another alternative would be something like this:
app.controller('SimpleController', ['$scope', function($scope) {
$scope.somevar = "something";
....
}]);
When you use $scope, you're making that property publicly available in the view.

Related

Testing javascript functions - that are not visible to controller (angular)

I am using the controller as syntax from angular and i want to test my code using jasmine and sinon.
Let's say i want the following controller code :
angular
.module('Test')
.controller('TestController', TestController);
TestController.$inject = [];
function TestController() {
var viewModel = this;
viewModel.myFunction = myFunction;
function myFunction(){
//do something
//now call a helper function
helperFunction()
}
function helperFunction(){
// ....
}
}
My question is how i can test the helperFunction() or even put a spy on it ? My helper is not visible in my test.
Here is my test :
(function () {
'use strict';
var myController;
describe('Test', function () {
beforeEach(module('Test'));
beforeEach(inject(function ($controller, $injector) {
myController = $controller('TestController');
}));
it('Tests helperFunction', function (){
var sinonSpy = sinon.spy(myController, 'helperFunction');
//perform the action
myController.myFunction();
//expect the function was called - once
expect(sinonSpy .callCount).toEqual(1);
}
})
})
You cannot have access to those functions. When you define a named JS function it's the same as saying:
var helperFunction = function(){};
In which case it would be pretty clear to see that the var is only in the scope within the block and there is no external reference to it from the wrapping controller.
To make a function testable, you need to add it to the $scope of the controller.
viewModel.helperFunction = helperFunction;
But be aware that is not a good idea to be exposing everything just to make it testable. You really need to consider if testing it will actually add some value to your project
try to do so :
var vm = controller("helperFunction", { $scope: scope });
and then:
vm.myFunction();
Add the following code into your controller:
angular.extend($scope, {
helperFunction:helperFunction
});

Binding with service variable using ControllerAs syntax without $scope?

I'm familiarizing myself with controllerAs syntax in AngularJS, and I've come to a problem when I need to do a simple binding to a service variable. Typically a $scope.$watch or $scope.$on would do, but that would involve injecting $scope, which seems to defeat the purpose of controllerAs.
Currently what I have is that after clicking one of the buttons and calling config.setAttribute(attr), the controller calls the service's setAttribute function, but not getAttribute, so config.attribute never changes.
Is there something I'm overlooking in how I'm approaching this? Would I need to inject $scope or change the controller syntax to use $scope instead?
View:
<div data-ng-controller="ConfigCtrl as config">
<h3>Customize</h3>
<pre>Current attribute: {{config.attribute}}</pre>
<label>Attributes</label>
<div data-ng-repeat="attr in config.attributes">
<button ng-click="config.setAttribute(attr)">{{attr.name}}</button>
</div>
</div>
Service:
(function() {
'use strict';
angular.module('app')
.factory('Customization', Customization);
function Customization() {
var service = {
attribute: null,
getAttributes: getAttributes,
setAttribute: setAttribute,
getAttribute: getAttribute
}
return service;
/////
function getAttributes() {
return [
{name: 'Attr1', value: '1'},
{name: 'Attr2', value: '2'} // etc.
];
}
function setAttribute(attr) {
service.attribute = attr;
}
function getAttribute() {
return service.attribute;
}
}})();
Controller:
(function(){
'use strict';
angular.module('app')
.controller('ConfigCtrl', ConfigCtrl);
function ConfigCtrl(Customization){
var vm = this;
vm.attribute = Customization.getAttribute(); // bind
vm.attributes = [];
// Functions
vm.setAttribute = Customization.setAttribute;
init();
/////
function init(){
// Get attributes array
vm.attributes = Customization.getAttributes();
}
}})();
Here is what my controller looks like after injecting $scope and adding the watch for attribute:
(function(){
'use strict';
angular.module('app')
.controller('ConfigCtrl', ConfigCtrl);
function ConfigCtrl($scope, Customization){
var vm = this;
vm.attribute;
vm.attributes = [];
// Functions
vm.setAttribute = Customization.setAttribute;
init();
/////
function init(){
// Get attributes array
vm.attributes = Customization.getAttributes();
}
$scope.$watch(function() {
return Customization.getAttribute()
}, function() {
vm.attribute = Customization.getAttribute();
});
}})();
I also have the Karma test in case anyone is interested:
(function() {
'use strict';
describe('ConfigCtrl', function () {
var ConfigCtrl, scope;
beforeEach(module('app'));
beforeEach(inject(function($rootScope, $controller) {
scope = $rootScope.$new();
ConfigCtrl = $controller('ConfigCtrl',
{$scope: scope}
);
}));
describe('#setAttribute', function(){
it('sets the current attribute', function(){
var selected = {
name:'Attr1',
value:'1'
};
ConfigCtrl.setAttribute(selected);
scope.$apply();
expect(ConfigCtrl.attribute).to.eql(selected);
});
});
});
})();
Thanks for the help. I'm welcome to any better answers anyone else might have.

http.jsonp and callbacks in javascript

I am working on a calculator that will consider AWS instance costs. I am pulling the data from a .js file on amazon and I would like to read it into an object but i keep getting an error "Uncaught ReferenceError: callback is not defined" .. here is my .js file.
(function() {
var app = angular.module('formExample', []);
var ExampleController = function($scope, $http) {
$scope.master = {};
$scope.update = function(user) {
$scope.master = angular.copy(user);
$scope.GetAws();
};
$scope.reset = function() {
$scope.user = "";
};
function callback(data) {
$scope.aws = data;
}
$scope.GetAws = function() {
var url = "http://a0.awsstatic.com/pricing/1/ec2/linux-od.min.js?callback=callback";
$http.jsonp(url);
};
$scope.reset();
};
app.controller('ExampleController', ['$scope', '$http', ExampleController]);
}());
It is weird that the aws link you are using supports jsonp but it does not take custom callback function name. (Atleast you can look up to find out if the query string they are looking for is callback or not). angular handles it when we provide callback=JSON_CALLBACK it gets translated to angular.callbacks_x which are exposed globally temporarily by angular to handle the request and resolve the promise accordingly. But for this the endpoint must take the callback argument and wrap the response in the same string and function invocation. However this endpoint does not seem to consider it and even without any callback it automatically wraps into default callback function invocation. So you would need to inject $window (Correct DI way) object and set callback function to it and ?callback=callback is irrelevant.
var ExampleController = function($scope, $http, $window) {
$scope.master = {};
//....
$window.callback = function(data) {
$scope.aws = data;
}
$scope.GetAws = function() {
var url = "http://a0.awsstatic.com/pricing/1/ec2/linux-od.min.js?callback=callback";
$http.jsonp(url);
};
$scope.reset();
};
app.controller('ExampleController', ['$scope', '$http', '$window', ExampleController]);
Plnkr
It is because the AWS script is looking to call a function called "callback" on the global scope (outside of Angular). Since your function is within the scope of another (IIFE) function, it cannot be accessed.
What I've done in a case like this is simply put the function in the global scope.
In cases where an application requires some API to have loaded before Angular can do it's magic and has a callback similar to your situation, I have done the following, manually bootstrapping Angular:
index.html
<script src="http://www.example.com/api?callback=myCallbackFunction"></script>
app.js
// callback function, in global (window) scope
function myCallbackFunction() {
// manually bootstrap Angular
angular.element(document).ready(function() {
angular.bootstrap(document, ['myApp']);
});
}
// your IIFE
(function() {
})();
Notice callback should be set in window scope.
So,one solution is like:
$scope.reset = function() {
$scope.user = "";
};
window.callback = function(data) {
$scope.aws = data;
}
$scope.GetAws = function() {
var url = "http://a0.awsstatic.com/pricing/1/ec2/linux-od.min.js?callback=callback";
$http.jsonp(url);
};

Mock action invoked on controller initialization in AngularJS in test

I'm having angularjs controller that basically looks like below
app.controller('MyCtrl', function($scope, service) {
$scope.positions = service.loadPositions(); // this calls $http internally
$scope.save = function() {
...
};
// other $scope functions here
});
Now every time I write test for any of the methods in $scope I need to stub service.loadPositions() like below:
it(should 'save modified position', function($controller, service, $rootScope) {
spyOn(service, 'loadPositions').andReturn(fakeData);
var scope = $rootScope.$new();
$controller('MyCtrl', {$scope: scope});
// test stuff here
})
Is there any way to avoid this first stubbing in every test? I mean If I already tested that this action is invoked on controller start, I don't really need stubbing this in every next test. This introduces a lot of repetition in each test.
EDIT
I stubmbled upon ngInit and I thought I could use it but it seems not to be recommended way do to such things, but I'm not sure why?
Use a beforeEach inside your describe function:
describe('My test', function() {
var $controller,
$rootScope,
serviceMock;
beforeEach(function() {
serviceMock = { loadPositions: function() {} };
spyOn(serviceMock, 'loadPositions').andReturn(fakeData);
inject(_$controller_, _$rootScope_) {
$rootScope = _$rootScope_.$new();
$controller = _$controller_('MyCtrl',
{$scope: $rootScope, service: serviceMock});
};
});
it('should save modified position', function() {
// test stuff here
});
});
Notice that I have moved the controller initialization to beforeEach as well so you don't have to do it again in every test.
Just in case you're wondering what the underscores in the inject arguments are for, they enable the test to declare a local $controller variable and a local $rootScope variable. Angular just ignores them when it's resolving the function dependencies.
Update: There was a little bug in the example code. Just fixed it.
You can move this into beforeEach() and use $provide to always return your fake service.
Not knowing all of your test code something like this should work.
var scope, controller;
beforeEach(module("app", function($provide){
var mockedService = {
loadPositions: function(){return fakeData;}//or use sinon
};
$provide.value('service', mockedService);
});
beforeEach(inject(function($rootScope, $controller) {
scope = $rootScope.$new();
controller = $controller;
}));
it(should 'save modified position', function() {
controller('MyCtrl', {$scope: scope});
// test stuff here
});

Testing Angular Controllers defined like angular.module('myApp').controller(

I am playing around with https://github.com/angular/angular-seed
A controller is defined in app/controllers.js like this
'use strict';
function MyCtrl1() {}
MyCtrl1.$inject = [];
this doesn't pass jshint as MyCtrl1 is referenced in app/app.js and not in my globals list.
According to Brian Ford and others I have read the preferred style is
angular.module('myApp').controller('MyCtrl1', [], function () {});
I like this better as it's not in the global scope, but now my testacular specs fail because this doesn't work anymore:
var myCtrl1;
beforeEach(function(){
myCtrl1 = new MyCtrl1();
});
How do I get a reference to this controller which is defined in the "preferred" style for testing purposes?
credit due to both Javito and Xesued:
beforeEach(module('myApp'));
var scope, ctrl;
beforeEach(inject(function($controller, $rootScope) {
scope = $rootScope.$new();
ctrl = $controller('MyCtrl1', {$scope: scope});
}));
Try,
beforeEach(inject(function($controller) {
scope = {};
MyCtrl1 = $controller('MyCtrl1', {
$scope: scope
});
}));

Categories

Resources