AngularJS Factory: undefined is not a function - angularjs

I wrote the following factory service in AngularJS, but when I try to call the factory service in my RootController, I got the "undefined is not a function" error in my console.
MyService.js:
(function() {
angular.module('serviceTestApp')
.factory('MyService', ['$resource', '$log', '$q', '$http', MyService]);
function MyService($log, $resource, $http, $q) {
var name = "Tom";
var getName = function() {
return name;
}; //getName
var changeName = function(newName) {
name = newName;
}; //changeName
var getIP = function() {
var deferredObj = $q.defer();
$resource('http://jsonip.com').query().$promise.then(function(result) {
deferredObj.resolve(result);
}, function(errorMsg) {
deferredObj.reject(errorMsg);
});
return deferredObj.promise;
}; //getIP
return {
getName: getName,
changeName: changeName,
getIP: getIP
};
}
}());
in my RootController, I try to call the services, and everything worked until I call the getIP() service - return a promise object. Does anyone see anything wrong?
RootController.js:
(function() {
angular.module('serviceTestApp')
.controller('RootCtrl', ['$http', '$log', '$scope', 'MyService', RootCtrl]);
function RootCtrl($log, $scope, $http, MyService) {
var vm = this;
vm.message = "hello world from RootController";
MyService.changeName("Henry Tudor");
vm.message = "my name is: " + MyService.getName();
MyService.getIP().query().then(function(data) {
$log.info('in the promise, ip is: ' + data.ip);
vm.message = vm.message + ', your IP is ' + data.ip;
}, function(error) {
vm.message = vm.message + ', error: ' + error;
});
}
}());

It may sound stupid, but I've located the cause for the error:
The API used in the $resource() returns a single JSON object:
{"ip":"2601:0:b840:8077:a97f:ee9c:f5b8:1643","about":"/about","Pro!":"http://getjsonip.com"}
however, the query() expects an array, not a JSON object.
After changing to another API that returns an array in JSON format, it works. Sigh, wasted my 2 hours.
thanks everyone

in order to use your service like that
MyService.getName()
that mean you have a service called MyService and returns a function called getName
but in your case it dosen't you have to change
your return to something like that
return {
getName: function() {return getName();},
changeName: function() {return changeName();},
getIP: function() {return getIP();}
};

Related

Service making the $scope not accessible on the html

Can't access the scope, for example putting {{ pagec }} on the html isn't working but when I remove blogpostservice from the controller it works fine again.
var app = angular.module('Blog', []);
app.factory('blogpostservice', ['$http', function ($http) {
this.getMoreData = function (pagecount) {
return $http.get('/api/posts/' + pagecount);
}
}]);
app.controller('MainController', ['$scope', 'blogpostservice',
function ($scope, blogpostservice) {
$scope.pagec = 1;
$scope.posts = [];
this.getMoreData = function (posts) {
blogpostservice.getMoreData(pagec).success(function () {
alert('got it successfully!!!');
}).error(function () {
alert('something went wrong!!!');
});
}
}]);
Because you had wrong factory implementation, factory should always return an object. You must have got an error in console(please check).
app.factory('blogpostservice', ['$http',
function ($http) {
function getMoreData (pagecount) {
return $http.get('/api/posts/' + pagecount);
}
return {
getMoreData: getMoreData
}
}
]);
Or you can convert your factory to service, there you need to bind data to this(context) like your were doing before.
app.service('blogpostservice', ['$http', function ($http) {
this.getMoreData = function (pagecount) {
return $http.get('/api/posts/' + pagecount);
}
}]);
Also don't use .success/.error on $http call, they are
deprecated. Instead use .then.

writing a simple Angular Service

OK, I've built services before but obviously I don't actually know what makes them tick, since I can't seem to debug this ultra-simple service call:
app.js:
var gridApp = angular.module('gridApp', []);
gridApp.controller('mainController', ['$scope', '$http', 'dataService',
function($scope, dataService) {
$scope.message = 'I am Angular and I am working.';
var init = function(){
console.log(dataService.foo);
console.log(dataService.getData());
};
init();
}]);
dataService.js:
(function() {
'use strict';
angular
.module('gridApp')
.service('dataService', dataService)
dataService.$inject = [];
function dataService() {
console.log("I am the dataService and I am loaded");
var foo = 1;
function getData () {
return 2;
}
}
})();
I see this on-screen: I am Angular and I am working. so Angular is loading.
I see this in console: I am the dataService and I am loaded so the dataService is actually being loaded.
But then the console.log is:
undefined (line 8)
TypeError: dataService.getData is not a function (line 9)
What am I missing?
The previous answers are correct in that your $http injection was wrong, but you are also not attaching your service functions to the service:
function dataService() {
var dataService = this; //get a reference to the service
//attach your functions and variables to the service reference
dataService.foo = 1;
dataService.getData = function() {
return 2;
};
}
An Angular service is very simply an object class. It is also a singleton, meaning it's instantiated only once per run of your app. When the service is instantiated it is very much akin to calling the new operator on your dataService "class":
var $dataService = new dataService();
So, when you inject dataService into your controller, you are actually getting an instance, $dataService, of your dataService "class".
See this blog entry for further reading: https://tylermcginnis.com/angularjs-factory-vs-service-vs-provider-5f426cfe6b8c#.sneoo52nk
You are missing the 2nd parameter $http in the function. The named parameters and the actual parameters in function need to be the same, same order and same number. What happened before is that dataService was being assigned an $http instance and the actual dataService was not injected at all because there was no 3rd parameter to inject it into.
var gridApp = angular.module('gridApp', []);
gridApp.controller('mainController', ['$scope', '$http', 'dataService',
function($scope, $http, dataService) {
// ----was missing-----^
$scope.message = 'I am Angular and I am working.';
var init = function(){
console.log(dataService.foo);
console.log(dataService.getData());
};
init();
}]);
We have missed the second param '$http' in function. Just add the '$http' param, it should work fine
var gridApp = angular.module('gridApp', []);
gridApp.controller('mainController', ['$scope', '$http', 'dataService',
function($scope,$http, dataService) {
$scope.message = 'I am Angular and I am working.';
var init = function(){
console.log(dataService.foo);
console.log(dataService.getData());
};
init();
}]);
This is how I've been taught to set up services:
function dataService() {
var dataService = {};
var _foo = 1;
var _getData = function () { return 2; }
dataService.foo = _foo;
dataService.getData = _getData;
return dataService;
}
I believe this facilitates public/private methods/vars.
For reference, this is the full code accessing my service:
app.js:
var gridApp = angular.module('gridApp', []);
// create the controller and inject Angular's $scope
gridApp.controller('mainController', ['$scope', 'dataService', function($scope, dataService) {
// create a message to display in our view
$scope.message = 'Angular is working';
var init = function(){
getPackageData();
};
var getPackageData = function (){
return dataService.getData().then(
function successCallback(response) {
console.log(response);
},
function errorCallback(response) {
console.log(response);
}
);
};
init();
}]);
dataService.js:
(function() {
'use strict';
angular
.module('gridApp')
.service('dataService', dataService)
dataService.$inject = ['$http'];
function dataService($http) {
var dataService = {};
var _getData = function () {
return $http({
method: 'GET',
url: 'data/packages.json'
})
.then(function successCallback(response) {
return response;
},
function errorCallback(response) {
return response;
});
}
dataService.getData = _getData;
return dataService;
}
})();

Unit testing two dependent services and controller in AngularJS

I have a demo application where I have a controller which has a factory as dependency and factory itself depends on another service. My code is as follows:
var app = angular.module('sampleApp', ['ui.router']);
app.service("someServ", function(){
this.sendMsg = function(name){
return "Hello " + name;
}
})
app.factory("appFactory", function ($http, someServ) {
function getData(url) {
return $http.get(url);
}
function foo(){
var text = someServ.sendMsg("Monotype");
alert(text);
}
return {
getData : getData,
foo : foo
}
})
var productsController = function ($scope, $http, appFactory) {
var pct = this;
pct.url = "http://mysafeinfo.com/api/data?list=englishmonarchs&format=json";
var jsonDataPromise = appFactory.getData(pct.url);
jsonDataPromise
.then(function (response) {
pct.jsonData = response.data;
}, function (err) {
console.log("Error is: " + error);
});
pct.profun = function(){
appFactory.foo();
}
};
app.controller("productsController", productsController);
productsController.$inject = ['$scope', '$http', 'appFactory'];
I have to test with karma using Jasmine 2.4 as testing framework. I have tried many online tutorials but getting totally confused as everyone tries to do something different. Some use $provide to mock the service, some simply inject the actual service/factory and use a reference, some do not give any example of passing arguments to services.
Can someone please tell me how to do unit testing in simple terms. I have already tried doing something like this:
describe('unit testing of service and controller', function(){
beforeEach(module('sampleApp'));
var prodCtrl, $prodScope, mockfactory, mockservice;
beforeEach(function(){
mockservice = {
sendMsg : function(name){
return name;
}
}
module(function($provide){
$provide.value("someServ", mockservice);
});
inject(function($rootScope, $controller, $http, $q, appFactory){
appFactory = appFactory;
spyOn(appFactory, 'getData');
spyOn(appFactory, 'foo');
$prodScope = $rootScope.$new();
prodCtrl = $controller('productsController', {
$scope: $prodScope, $http: $http, appFactory:appFactory
});
});
});
it('appFactory has method getData and foo', function(){
appFactory.getData();
appFactory.foo();
expect(appFactory.getData).toHaveBeenCalled();
expect(appFactory.foo).toHaveBeenCalled();
})
it('productsController gets a promise back from getData', function(){
var url = "sample url";
var myPromise = prodCtrl.getData(url);
myPromise.then(function(){console.log("Promise returned");})
})
it('foo calls service method sendMsg', function(){
prodCtrl.profun();
expect(mockservice.sendMsg).toHaveBeenCalled();
})
});
I was finally able to solve this issue. My code looks like this:
var app = angular.module('sampleApp', []);
app.service("someServ", function(){
this.sendMsg = function(name){
return "Hello " + name;
}
})
app.factory("appFactory", function ($q, someServ) {
function getData() {
var defer = $q.defer();
defer.resolve("Success message");
return defer.promise;
}
function foo(){
var text = someServ.sendMsg("Monotype");
alert(text);
}
return {
getData : getData,
foo : foo
}
})
app.controller("mainController", ['$scope', '$http','appFactory', function($scope, $http, appFactory){
var mct = this;
mct.printData = function(){
var myPromise = appFactory.getData();
myPromise
.then(function(data){
alert("Promise returned successfully. Data : " + data);
}, function(error){
alert("Something went wrong.... Error: " + error);
})
}
mct.showMsg = function(){
appFactory.foo();
}
}]);
The test case looked like this:
describe('unit testing', function(){
var jsonData = {
name: "Aosis",
id: 12345
}
beforeEach(module('sampleApp'));
beforeEach(module(function($provide){
$provide.service("someServ", function(){
//this.sendMsg = function(param){}
this.sendMsg = jasmine.createSpy('sendMsg').and.callFake(function(param){})
});
$provide.factory("appFactory", function(someServ, $q){
function getData(){
var defer = $q.defer();
defer.resolve("Success message");
return defer.promise;
}
function foo(){
var facParam = "some text";
someServ.sendMsg(facParam);
}
return {
getData : getData,
foo : foo
}
});
}));
var $scope, mainController, appFactoryMock, someServMock;
beforeEach(inject(function($rootScope, $controller, $http, $q, appFactory, someServ){
appFactoryMock = appFactory;
someServMock = someServ;
$scope = $rootScope.$new();
mainController = $controller("mainController", {
$scope : $scope,
$http : $http,
appFactory : appFactoryMock
});
}));
// Tests go here....
});
Here, I have mocked service method as jasmine spy and specified the function that should get executed usingand.callFake(function(){.....}). A fake factory has been created and its methods have been spied upon. I tried to create fake factory similar to service using jasmine.createSpy but return {
getData : getData,
foo : foo
} was giving error. Hence, I did that.
Anyone, else if has better solution or some other explanation, please share.

Content of an XML File with $http not available outside the .succes() context

I have a service xmlService which uses the $http service to read an XML-File. Everything is working fine, but when I use my xmlService in one of my controllers it returns an empty object, although the $http.get() is successfull. I have no idea whats the problem.
Update:
I understood that the $http-call is asyncronous thus I have to use the $q service. I'm not sure if I understood the $q API correctly because my updated code doesn't work either. I also read an tutorial like this one: http://www.bennadel.com/blog/2612-using-the-http-service-in-angularjs-to-make-ajax-requests.htm - But I can't find the mistake, because I probably didn't understand something.
My Service
angular.module('tvc.Services.Xml', []);
angular.module('tvc.Services.Xml')
.service('xmlService', ['$http', '$log', '$q', 'x2js', function($http, $log, $q, x2js) {
return function(file) {
var deferred = $q.defer(),
parsedFile = {};
$http
.get(file).success(function(data) {
parsedFile.fileContent = data;
parsedFile.json = x2js.xml_str2json(data);
deferred.resolve(parsedFile);
})
.error(function(data) {
$log.warn('Unable to load: ' + file);
});
return deferred.promise;
};
}]);
console.log(parsedFile); returns:
{
fileContent: '<xml>...</xml>',
json: {..},
__prototype__: {...}
}
My Controller
.controller('KeyaccountsController', ['$scope', 'xmlService', function($scope, xmlService) {
$scope.keyaccounts = {};
xmlService('assets/xml/ops_merge_data2.xml').then(function(data) {
$scope.keyaccounts = data;
});
console.log($scope.keyaccounts);
}]);
console.log($scope.keyaccounts); returns:
{}
Since the $http.get() is asynchronous, the return is happening before the success happens, and thus parsedFile is undefined.
You either need to return the promise (i.e., return $http.get(file) and then do all of the logic that you're doing in your controller, or use the angular $q service, like this:
angular.module('tvc.Services.Xml', []);
angular.module('tvc.Services.Xml')
.service('xmlService', ['$http', '$log', 'x2js', '$q', function($http, $log, x2js, $q) {
return function(file) {
var deferred = $q.defer();
var parsedFile = {};
$http
.get(file).success(function(data) {
parsedFile.fileContent = data;
parsedFile.json = x2js.xml_str2json(data);
deferred.resolve(parsedFile)
})
.error(function(data) {
$log.warn('Unable to load: ' + file);
});
return deferred.promise;
};
}]);
And then your controller would be like:
.controller('KeyaccountsController', ['$scope', 'xmlService', function($scope, xmlService) {
xmlService('assets/xml/ops_merge_data2.xml').then(function(data) {
$scope.keyaccounts = data;
console.log($scope.keyaccounts);
});
}]);

How can a service return data and multiple promises to a controller?

I have defined a service with functions like this:
angular.module('common').factory('_o', ['$angularCacheFactory', '$http', '$q', '$resource', '$timeout', '_u',
function ($angularCacheFactory, $http, $q, $resource, $timeout, _u) {
var _getContentTypes = function ($scope) {
var defer = $q.defer();
$http.get('/api/ContentType/GetSelect', { cache: _u.oyc })
.success(function (data) {
$scope.option.contentTypes = data;
$scope.option.contentTypesPlus = [{ id: 0, name: '*' }].concat(data);
$scope.option.sContentType = parseInt(_u.oyc.get('sContentType')) || 0;
defer.resolve();
})
return defer.promise;
};
return {
getContentTypes: _getContentTypes
}
}]);
I am calling this in my controller like this:
.controller('AdminProblemController', ['$http', '$q', '$resource', '$rootScope', '$scope', '_g', '_o', '_u',
function ($http, $q, $resource, $rootScope, $scope, _g, _o, _u) {
$scope.entityType = "Problem";
_u.oyc.put('adminPage', $scope.entityType.toLowerCase());
$q.all([
_o.getContentTypes($scope),
_o.getABC($scope),
_o.getDEF($scope)
])
Am I correct in saying this is not the best way to use a service. I think I should be returning the
content type data and then in the controller assigning to the scope not in the service.
But I am not sure how to do this as my service just returns a defer.promise and I am using $q.all so I think I should populate the scope after $q.all has returned success for every call.
Can someone give me some advice on how I should return data from a service with a promise and have it populate the $scope after $q.all has completed with all calls successful ?
You are absolutely correct in saying that the controller should really be doing this, it would be much cleaner to remove the passing around of your scope (and make it more re-usable). I don't know your exact use case and it is a little confusing to read, but you can do this by hooking into the promises that are created by $http, as well as still handling when all of the promises have been completed.
fiddle: http://jsfiddle.net/PtM8N/3/
HTML
<div ng-app="myApp" ng-controller="Ctrl">
{{model | json}}
<div ng-show="loading">Loading...</div>
</div>
Angular
var app = angular.module("myApp", []);
app.service("_service", ["$http", function (http) {
this.firstRequest = function () {
return http.get("http://json.ph/json?delay=1000")
.then(function (res) {
// manipulate data
res.data.something = new Date();
return res.data;
});
};
this.secondRequest = function () {
return http.get("http://json.ph/json?delay=2000")
.then(function (res) {
// manipulate data
res.data.something = 12345;
return res.data;
});
};
this.thirdRequest = function () {
return http.get("http://json.ph/json?delay=3000")
.then(function (res) {
// manipulate data
res.data.something = "bacon";
return res.data;
});
};
}]);
app.controller("Ctrl", ["$scope", "_service", "$q", function (scope, service, q) {
scope.loading = true;
scope.model = {};
var firstRequest = service.firstRequest();
var secondRequest = service.secondRequest();
var thirdRequest = service.thirdRequest();
q.all([firstRequest, secondRequest, thirdRequest]).then(function (responses) {
scope.model.first = responses[0];
scope.model.second = responses[1];
scope.model.third = responses[2];
scope.loading = false;
});
}]);

Resources