How to properly access AngularJS service in es6 - javascript

I'm trying to access the properties of my service, I think theres something wrong with the way I'm injecting the service into my class. I get the following error message when running my app
angular.js:13424 ReferenceError: CouponsService is not defined
at CouponsComponent.$onInit (coupons.controller.js:13)
...
Service
angular.module('couponGeneratorApp')
.service('CouponsService', function () {
// AngularJS will instantiate a singleton by calling "new" on this function
this.amount_type = null;
this.amount = null;
this.has_min = null;
this.min_order = null;
});
Controller
(function() {
class CouponsComponent {
constructor($state, CouponsService) {
this.test = 'hello';
this.state = $state;
this.couponParams = {};
}
$onInit() {
console.log(CouponsService);
}
processParams() {
if (!this.couponParams.amount || this.couponParams.amount <= 0) {
alert('Enter a valid amount');
} else if (this.couponParams.has_min && (!this.couponParams.min_order || this.couponParams.min_order < 0)) {
alert('Enter a valid min order');
} else {
CouponsService.amount_type = this.couponParams.amount_type;
CouponsService.amount = this.couponParams.amount;
CouponsService.has_min = this.couponParams.has_min;
if (CouponsService.has_min) CouponsService.min_order = this.couponParams.min_order;
this.state.go('coupons.login');
}
}
}
angular.module('couponGeneratorApp')
.component('couponsForm', {
templateUrl: 'app/coupons/form.html',
controller: CouponsComponent
});
angular.module('couponGeneratorApp')
.component('couponsLogin', {
templateUrl: 'app/coupons/login.html',
controller: CouponsComponent
});
})();

The issue is with the scope of the variable. When you inject it into an ES6 class, the constructor method doesn't make the variable available to all other methods. So just like you set $state to this.$state, you need to do the same for any injected services that other methods will use.
class CouponsComponent {
constructor($state, CouponsService) {
this.test = 'hello';
this.state = $state;
this.CouponsService = CouponsService;
this.couponParams = {};
}
$onInit() {
console.log(this.CouponsService);
}
// Rest of class
}
Also recommend to use ngAnnotate so your build tool can help make the injection work better.

You have to decorate your class with the injectable service.
Add:
CouponComponent.$inject = ['$state', 'CouponService'];

Related

How to fix the unit test issue in my case?

I am trying to unit test two functions codes and keep getting error of undefined object.
my controller
vm = this;
//always fire first in the app
vm.getCompany = function() {
api.getCompany(function(res){
//do stuff
})
}
//always fire second in the app
vm.getEmployee = function() {
api.getEmployee(function(res){
//do stuff
})
}
api service
var company;
function getCompany() {
var company;
var q = $q.defer();
var url = ‘something.com’;
anotherApi.getCompany(url).then(function(comp){
company = comp;
q.resolve(company)
})
}
function getEmployee = function() {
var name = company.name
var url = ‘something.com/’ + name;
var q = $q.defer();
anotherApi.getEmployee(url).then(function(employee){
q.resolve(employee)
})
}
unit test.
beforeEach(function(){
module(‘myApp);
inject(function ($injector) {
$controller = $injector.get('$controller');
$rootScope = $injector.get('$rootScope');
$scope = $rootScope.$new();
$httpBackend = $injector.get('$httpBackend');
api = $injector.get('api');
});
vm = $controller'myCtrl', {
$scope : $scope
});
})
describe (‘test’, function(){
it(‘should get company’, function(){
vm.getCompany();
$httpBackend.flush();
// stuff and works
})
it(‘should get Employee’, function(){
vm.getEmployee()
$httpBackend.flush();
//getting error says
//'undefined' is not an object (evaluating 'company.name’)
})
})
I am getting 'undefined' is not an object (evaluating 'company.name’)
under getEmployee function in service.
I have tried many different ways but still not sure how to fix it, can someone help me about it? Thanks!
What is the expected behavior of the service if getEmployee is called before getCompany is called? You should at least check for company being null before attempting to use it. Also, you may want to consider storing the company in a property that you can access in your service. NOTE: I'm prefixing the property name with an underscore just to make a distinction between the public api and this pseudo-private property:
{
_company: null,
getCompany: function() {
var self = this;
var url = '...';
return $http.get(url).then(function(comp){
self._company = comp;
return self._company;
});
},
getEmployee: function() {
var self = this;
if (!self._company) {
return null; //or throw error or return rejected promise: $q.reject('company is null')
} else {
var url = '...';
var name = self._company.name;
return http.get(url);
}
}
}
Lastly, you can (and should) test your service separately from your controller now. In your controller test, you can just spyOn your service methods without it calling through to the server. And when you test your service, you can just set the service._company to a mock value when testing the getEmployee method.
Issue is in your Service. "company" should be the object literal since you access .name over it else it will through an error which you have specified.
Try below code:
Service
var company = {};
function getCompany() {
$http.get(url).then(function(comp){
company = comp;
return company;
})
}
function getEmployee = function() {
var name = company.name
$http.get(url).then(function(employee){
// do stuff
}
}
It should work.

How to create unit-testing for array function in AngularJS with Jasmine

I have xf array: var xf = [];
And I have a function is a element in this array and a function to use it:
$scope.checkEmailValid = function () {
var result = false;
Iif (xf.validateEmail($scope.email, '256')) {
result = true;
}
return result;
};
xf.validateUsername = function (sText) {
var isValid = false;
do {
//Check for valid string.
isValid = typeof sText === 'string';
if (!isValid) {
break;
}
//Check that each special character does not exist in string.
for (var i = 0; i < sText.length; i++) {
if (xf.SPECIAL_CHARS.indexOf(sText.charAt(i)) !== -1) {
isValid = false;
break;
}
}
if (!isValid) {
break;
}
} while (false);
return isValid;
};
But when I run my spec:
it ('checkEmail', function(){
$controller('MyCtrl', { $scope: $scope });
xf.validateUsername();
spyOn(window,xf.validateUsername).and.callThrough();
});
It makes an error:
xf.validateUsername is not a function
How can I cover it?
The xf variable is not acessible from the outside of the controller's scope (i.e. not accessible in the unit test files).
You must've done the following thing:
angular
.module('myModule')
.controller(function ($scope) {
var xf = [];
// etc.
});
You could attach the xf variable to the MyController instance once Angular instantiates it:
angular
.module('myModule')
.controller(function ($scope) {
this.xf = [];
// etc.
});
But that's not really a clean way of doing it. A better way (in my opinion) would be to create a factory:
angular
.module('myModule')
.factory('xfService', function () {
var xf = [];
function validateUsername(text) {
// etc.
}
function get() {
return xf;
}
return {
get: get,
validateUsername: validateUsername
};
});
Now, you can inject the factory in your controller to use xf:
angular
.module('myModule')
.controller(function ($scope, xfService) {
// somewhere
var isValid = xfService.validateEmail($scope.email, '256');
// to get the values in the array
var values = xfService.get();
});
Finally, for the unit tests, it becomes really easy to test the validateEmail method.
describe('Unit tests - xfService', function () {
var xfService;
beforeEach(angular.module('myModule'));
beforeEach(angular.inject(function (_xfService_) {
xfService = _xfService_;
});
});
describe('xfService.validateUsername', function () {
it('should return a boolean value', function () {
// for example
expect(typeof xfService.validateUsername('test')).toBe('boolean');
});
// add more unit tests to check that the method works as expected
});
});
You'll need to add the angular-mocks file to the Karma config.
Thanks to Paul Podlech and Claies for the hints in the comments/answers.
I'm not sure to completely understand your question. But there are a few thinks i think you are doing wrong:
If xf it's a global variable you should mock it, since you are testing the controller, not the global variable.
If you want to check the real function of your global variable, go to the karma.conf.js file and add the js file path to the files option:
files: [
...,
'fx-script.js',
...
],
callThrough should be used before the actual function is invoked:
it ('checkEmail', function(){
var ctrl = $controller('MyCtrl', { $scope: $scope });
spyOn(window, ctrl.xf.validateUsername).and.callThrough();
ctrl.xf.validateUsername();
});
I recommend you to separately test your controller, service, or global scripts, and add mocks whenever you need to inject a dependency or global variable, so if you can tell for sure which module/script is failing any time.
you should move functionality in xf into separate service/factory. Then inject it in controller. That makes it pretty easy to mock it while testing.
Try this in the controller
var xf = this.xf = [];
and this in your test
it ('checkEmail', function(){
var xf = $controller('MyCtrl', { $scope: $scope }).xf;
spyOn(xf, 'validateUsername').and.callThrough();
xf.validateUsername();
});
But you should realize that this exposes your xf object on the Controller as mentioned in the comment of Claies.

Angularjs $rootScope is invisible in controllers

everyone!
I'm creating app with authorization.
To store user data during session I use such service:
app.service('Session', function ($rootScope, $cookies) {
var Session = $rootScope.$new();
Session.createSession = function (accessTo, level, user_data) {
this.level = level;
this.accessTo = accessTo;
if(user_data) {
$cookies.isAuth = true;
} else {
$cookies.isAuth = false;
}
this.user = {
email : user_data.email,
fullname : user_data.fullname,
id : user_data.id,
level : user_data.level
}
if(accessTo && accessTo.length == 1 && !$cookies.account) {
this.account = accessTo[0].id;
$cookies.account = accessTo[0].id;
} else {
this.account = $cookies.account;
}
};
Session.destroy = function () {
this.level = null;
this.account = null;
this.user = null;
this.isAuth = false;
$cookies.account = null;
};
return Session;
});
In controller use:
Session.createSession(some, params, here);
Afteer that it put some data to rootscope, and i can show it in console, but when i try to see for example Session.user/.level etc. it doesn't work. What is wrong?
You should be doing this:
app.service('Session', function() {
// A service is a singleton, as in it a persistent
// object you can inject in controllers
this.foo = 'bar'; // anything on "this" is publicly available
this.createSession = function() { ... };
// you can also expose functions that you can call from the controllers
});
app.controller('Ctrl1', function($scope, Session) {
// You can inject "Session" here and use stuff you
// added on "this" inside of it
$scope.foo = Session.foo;
// you can put stuff on the controller's $scope
// and you can bind to it in your HTML by using <span>{{foo}}</span>
Session.foo = 'baz';
// you can change stuff in the service and it will be persisted
});
So now if you navigate to a certain "Ctrl2" that injects "Session" too, Session.foo will be "baz", not the initial "bar" value.

How can I change my AngularJS service to use Typescript?

I have this service:
app.factory('utilityService', [
'$http',
'$angularCacheFactory',
utilityService
]);
function utilityService(
$http,
$angularCacheFactory
) {
var factory = {};
factory.rowClicked = function ($index, collection) {
var row = collection[$index];
if (row.current) {
row.current = false;
return null;
} else {
collection.forEach(function (object) {
object.current = false;
});
row.current = true;
return $index;
}
};
factory.isArrayAndNotEmpty = function (theArray) {
var rc = typeof theArray !== 'undefined' && theArray.length > 0;
return rc;
};
return factory;
}
Typescript appears to recognize everything okay until it comes to the rowClicked (and all the other functions which I didn't show here to save space). For these it gives the message:
factory.rowClicked
The property rowClicked does not exist on value of type '{}'
var factory = {};
TypeScript infers the type of factory from its initializer. Here, you've said that factory has no members, so any attempt to read or write a property on it is going to fail.
You could simply change the type of factory to be any:
var factory: any = {};
This will make it valid to read or write any property of factory, but that might be too permissive. You could also write out the type of it:
var factory: {
rowClicked($index, collection);
} = <any>{};
If you have types for $index and collection, you could improve the type further:
var factory: {
rowClicked($index: MyIndexService, collection: MyRowCollectionType);
} = <any>{};
You can use TypeScript classes for services and you will get greater TypeSafety without needing TypeAssertions (or manual interfaces).
You will need to use angular.service instead of angular.factory. Reference : https://www.youtube.com/watch?v=Yis8m3BdnEM&hd=1

Angular JS factory and variable access

I recently changed my factory FROM
app.factory('controllerComm', function($rootScope)
{
var showVforum = {};
showVforum.result = false;
showVforum.prepBroadcast = function(val)
{
this.result = val;
this.broadcastVal();
}
showVforum.broadcastVal = function()
{
$rootScope.$broadcast('toggleVforum')
}
return showVforum;
});
To THIS
app.factory('controllerComm', ['$rootScope', function($rootScope)
{
var showVforum = {};
showVforum.result = false;
showVforum.prepBroadcast = function(val)
{
this.result = val;
this.broadcastVal();
}
showVforum.broadcastVal = function()
{
$rootScope.$broadcast('toggleVforum')
}
return showVforum;
}]);
I did this for JS minification reasons. Before I changed it, I had this working in one of my controllers:
$scope.$on('toggleVforum', function()
{
$scope.isVisible = controllerComm.result;
$('#vforum').verticalAlign();
player.play();
});
controllerComm.result is now returning undefined since I changed my factory and I cannot figure out why. Any ideas?
edit
the error:
TypeError: Object function e(e,f,i){var j=c.defer(),k=j.promise,l=y(i)&&!i,f=a.defer(function(){try{j.resolve(e())}catch(a){j.reject(a),d(a)}l||b.$apply()},f),i=function(){delete g[k.$$timeoutId]};
k.$$timeoutId=f;g[f]=j;k.then(i,i);return k} has no method 'prepBroadcast'
at Object.$scope.hideVforum (http://localhost/aventos/resources/js/aventos.js:645:20)
at http://localhost/aventos/resources/js/angular.min.js:72:251
at http://localhost/aventos/resources/js/angular.min.js:144:140
at Object.e.$eval (http://localhost/aventos/resources/js/angular.min.js:88:347)
at Object.e.$apply (http://localhost/aventos/resources/js/angular.min.js:88:454)
at HTMLButtonElement.<anonymous> (http://localhost/aventos/resources/js/angular.min.js:144:122)
at HTMLButtonElement.x.event.dispatch (http://localhost/aventos/resources/js/jquery-1.10.2.min.js:5:14129)
at HTMLButtonElement.v.handle (http://localhost/aventos/resources/js/jquery-1.10.2.min.js:5:10866)
Try passing the result variable with the broadcast.
$rootScope.$broadcast('toggleVForum',{result: this.result});
controllerComm is not defined in the $on listener even though you may be injecting it into the controller where the listener is defined.
$scope.$on('toggleVForum',function(evt,args){
$scope.isVisible = args.result;
...
});

Categories