I am new to AngularJS and am trying to write a code to extract a data from JSON file.
I wrote a GET function and now want to call the GET function outside of the function.
I have a getData function and on the last line, there is var questions = getData'~~~'. I think this is wrong in my code. How can I call the getData function out side of the DataFactory function.
(function(){
angular
.module("GrammarQuiz")
.factory("DataService", DataFactory);
function DataFactory($log, $http){
var vm = this
var dataObj = {
questions: questions
};
vm.sort = sort;
vm.random = random;
vm.getData = getData;
var temp = 0;
// right now I have questions variable here
// but I want to move this to the outside of the function
//var questions = getData('data1.json');
function getData(apicall){
$log.log('begin!!!');
$http.get('api/' + apicall,
{headers:
{token: 'check!'}
}
).then(function(response){
$log.log(response.data);
questions = response.data;
}, function(response){
$log.log(response.data || "Request failed");
});
}
function sort(array) {
return array.sort(function() {
return .5 - Math.random();
});
}
function random() {
for (var key in dataObj.questions) {
dataObj.questions[key].Choices = sort(dataObj.questions[key].Choices);
}
}
random();
return dataObj;
}
var questions = DataFactory.getData('data1.json');
})();
As I mentioned in my comment, you need to inject your service into your controller. Something like this works:
(function(){
var myApp = angular.module('myApp',[]);
angular
.module("myApp")
.controller("MyCtrl", MyCtrl);
MyCtrl.$inject = ["myApp.myService"]; //injects your service into your controller
function MyCtrl(dataservice) {
var vm = this;
vm.name = 'Superhero';
//calls the service
dataservice.getData();
}
angular.module("myApp").factory("myApp.myService", function() {
//exposes the service's methods
//you need this, vs the vm syntax in your service
var service = {
getData: getData
};
return service;
function getData(){
alert("S");
}
});
})();
JSFiddle: http://jsfiddle.net/Lvc0u55v/8234/
You need to make your api calls in a 'Factory' or 'Services' file. Then make a call to the
'get' method in the Factory file in the 'Controller' file. Code separation is necessary, so
take advantage of the Factories and Controllers.
Refer to example below :
# user.factory.js
# 'app.foo.user' refers to your directory structure i.e. app/foo/user/user.factory.js
(function() {
'use strict';
angular
.module('app.foo.user', [])
.factory('userSvc', UserService);
/* #ngInject */
function UserService(
$log,
$q,
$http,
$window,
$state,
logger,
session,
utils,
API_CONFIG) {
var ENDPOINTS = {
USERS: '/v1/users'
};
/**
* #ngdoc service
* #name app.foo.user
* #description User management
*/
var service = {
get: get
};
/**
* #ngdoc method
* #name get
* #description Returns all users
* #methodOf app.foo.user
* #returms {promise} user or null if not found
*/
function get() {
var q = $q.defer();
var request = utils.buildAuthRequest( session, 'GET', ENDPOINTS.USERS );
$http(request)
.success(function (users) {
q.resolve(users.result);
})
.error(function (error) {
logger.error('UserService.get > Error ', error);
return q.promise;
}
}
})();
//------------------------------------------------------------------------------------
# user.module.js
# 'app.foo.user' refers to your directory structure i.e. app/foo/user/user.module.js
(function() {
'use strict';
angular
.module('app.foo.user', [
]);
})();
//------------------------------------------------------------------------------------
# user-list.controller.js
# This is where you make a call to the 'get' method in the 'user.factory.js'.
# And you gave to inject 'userSvc' in this file so as to connect to the 'user.factory.js' file.
# 'app.foo.admin' refers to your directory structure i.e. app/foo/admin/user-list.controller.js
(function() {
'use strict';
angular
.module('app.foo.admin')
.controller('UsersListController', UsersListController);
/* #ngInject */
function UsersListController(
$scope,
$state,
$timeout,
$log,
userSvc) {
var vm = this;
vm.loading = false;
vm.userSvc = userSvc;
activate();
function activate() {
// init users
vm.userSvc.get().then(
function(users) {
initSearchString(users);
vm.users = users;
},
function(error) {
$log.error(error);
}
);
}
}
})();
Related
I have the next 'problem' with Angular 1.
I have this Factory that I use to get the data for the current logged user:
angular.module('myModule')
.factory('authFactory', function ($http, $rootScope, Session, api, backend_url) {
var authFactory = this;
var user = {};
authFactory.init = function(){
// This API returns the information of the current user
api.current_user.get({}).$promise.then(function(res){
user = res;
});
}
// I use this function to return the user
authFactory.user = function () {
return user;
};
}
This is a basic Controller example where I'm trying to access the information retrieved by the above factory:
angular.module('myModule.mypage')
.controller('PageCtrl', function ($scope, authFactory) {
$scope.user = authFactory.user();
authFactory.init();
angular.element(document).ready(function () {
// This will return {} because it's called
// before the factory updates user value
console.log(authFactory.user());
console.log($scope.user);
});
});
The problem is that $scope.user = myFactory.user(); is not being updated once the Factory retrieve the user value.
I think my issue is related with myFactory.user();. I'm using a function, so the value returned by the function is not updated after myFactory.user has changed, I think that's why on PageCtrl the variable $scope.user is not getting any value.
My questions are:
Which is the best approach on my controller to wait until the user info is loaded by authFactory ?
Should I use a service instead ?
Problem with your implementation is that user is being initialized when authFactory.init() is invoked using presumably asynchronous API.
I would suggest you to return promise from authFactory.user method.
angular.module('myModule')
.factory('authFactory', function ($http, $rootScope, Session, api, $q, backend_url) {
var authFactory = this;
var user = {};
authFactory.init = function () {
// This API returns the information of the current user
return api.current_user.get({}).$promise.then(function (res) {
user = res;
});
}
//Return promise from the method
authFactory.user = function () {
var deferred = $q.defer();
if (angular.isDefined(user)) {
deferred.resolve(user);
} else {
authFactory.init().then(function () {
deferred.resolve(user);
});
}
return deferred.promise;
};
});
Then modify controller
angular.module('myModule.mypage')
.controller('PageCtrl', function ($scope, authFactory) {
authFactory.user().then(function (user) {
$scope.user = user;
})
});
angular.module('myModule')
.factory('authFactory', function ($http, $rootScope, Session, api, backend_url) {
var authFactory = this;
authFactory.user = {}
// I use this function to return the user
authFactory.getUser() = function () {
return api.current_user.get({}).$promise.then(function(res){
authFactory.user = res;
});
};
}
angular.module('myModule.mypage')
.controller('PageCtrl', function ($scope, authFactory) {
authFactory.getUser().then(function() {
$scope.user = authFactory.user;
});
});
Provide us a JSFiddle, I tried to help you without any testing environment.
I've written a couple of these tests previously on other controllers and it works just fine. But on this more complex controller, no matter which function I test it on, the .callFake using $q is not entering into the .then block and so no logic in there is being executed at all. I know the controller works for sure as this is on a production website. So why doesn't my test even enter the .then block when I've returned the deferred promise?
Controller Code -
(function() {
'use strict';
angular
.module('myApp')
.controller('EntryAddController', EntryAddController);
function EntryAddController($scope, $state, moment, $sanitize, EntryFactory, $modal, toastr, $log) {
var vm = this;
var now = moment();
var $currentYear = now.year();
vm.queues = [];
vm.calculating = false;
vm.total = 0;
vm.saving = false;
vm.addTxt = 'Confirm Entry';
var tomorrow = now.add(1, 'days').toDate();
var to = now.subtract(1, 'days').add(12, 'months').toDate();
vm.fromDate = tomorrow;
vm.toDate = to;
activate();
////////////////
function activate() {
var queueCache = {};
vm.updateEntrys = function() {
var payload = {
'from': moment(vm.fromDate).format('MM/DD/YYYY'),
'to': moment(vm.toDate).format('MM/DD/YYYY'),
'freq': vm.frequency.value,
'total': vm.total_amount
};
var key = JSON.stringify(payload);
if (!(key in queueCache)) {
EntryFactory.getEntryQueue(payload)
.then(function(resp) {
//LOGIC HERE BUT TEST NEVER ENTERS HERE DESPITE USING $Q
});
} else {
vm.queues = queueCache[key].queue;
vm.total = queueCache[key].total;
vm.calculating = false;
}
}
}
}
})();
Test Code
(function() {
'use strict';
describe('Entry Add Controller Spec', function(){
var vm;
var $controller;
var $q;
var $rootScope;
var EntryFactory;
var $scope;
var toastr;
beforeEach(module('myApp'));
beforeEach(inject(function(_$controller_, _$q_, _$rootScope_, _EntryFactory_) {
$controller = _$controller_;
$rootScope = _$rootScope_;
$scope = _$rootScope_.$new();
$q = _$q_;
EntryFactory = _EntryFactory_;
vm = $controller('EntryAddController', { $scope: $scope });
}));
it('expect EntryFactory.getEntryQueue to correctly set queues and total upon successful response', function() {
var payload = "blah";
var resp = {
"data": 1
}
spyOn(EntryFactory, 'getEntryQueue').and.callFake(function(payload) {
var deferred = $q.defer();
deferred.resolve(resp);
return deferred.promise;
});
EntryFactory.getEntryQueue(payload);
$rootScope.$apply();
//expect logic in .then to have worked
});
});
})();
Edit
Just thought of something... is this because I'm calling the factory function (EntryFactory.getEntryQueue) directly in the test, instead of calling the vm.updateEntrys function around it and therefore it doesn't ever proceed onto the .then portion of the code?
You allways need to consider what your SUT (System/Software under test) is.
In this case it is a controller's component method vm.updateEntrys.
It calls your Mocked EntryFactory.getEntryQueue() method. Also your code is not complete (where do you define vm.frequency object?). Nevertheless I have tested this method with the following test :
/*property
$apply, $log, $modal, $new, $sanitize, $scope, $state, EntryFactory, and,
callFake, data, defer, getEntryQueue, moment, promise, resolve, toastr,
updateEntrys
*/
(function () {
'use strict';
describe('Entry Add Controller Spec', function () {
var vm;
var $controller;
var $q;
var $rootScope;
var EntryFactory;
var $scope;
var toastr;
beforeEach(module('app.controllers'));
beforeEach(inject(function (_$controller_, _$q_, _$rootScope_) {
$controller = _$controller_;
$rootScope = _$rootScope_;
$q = _$q_;
}));
beforeEach(function () {
$scope = $rootScope.$new();
EntryFactory = {
getEntryQueue: function () {
}
};
moment = function () {
return {
format: function () {
}
}
}
vm = $controller('EntryAddController', { '$scope': $scope, '$state': {}, 'moment': moment, '$sanitize': {}, 'EntryFactory': EntryFactory, '$modal': {}, 'toastr': {}, '$log': {} });
});
it('expect EntryFactory.getEntryQueue to correctly set queues and total upon successful response', function () {
var payload = "blah";
var resp = {
"data": 1
};
spyOn(EntryFactory, 'getEntryQueue').and.callFake(function (payload) {
var deferred = $q.defer();
deferred.resolve(resp);
return deferred.promise;
});
vm.updateEntrys();
EntryFactory.getEntryQueue(payload);
$scope.$apply();
//expect logic in .then to have worked
});
});
})();
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/
I have the following service:
app.service('Library', ['$http', function($http) {
this.fonts = [];
this.families = [];
// ... some common CRUD functions here ...
// Returns the font list
this.getFonts = function() {
if(_.isEmpty(this.fonts)) this.updateFonts();
return this.fonts;
};
// Returns the family list
this.getFamilies = function() {
if(_.isEmpty(this.families)) this.updateFamilies();
return this.families;
};
// Update the font list
this.updateFonts = function() {
var self = this;
$http.get(BACKEND_URL+'/fonts').success(function(data) {
self.fonts = data;
console.log('Library:: fonts updated', self.fonts);
});
};
// Update the family
this.updateFamilies = function() {
var self = this;
$http.get(BACKEND_URL+'/families').success(function(data) {
var sorted = _.sortBy(data, function(item) { return item });
self.families = sorted;
console.log('Library:: families updated', self.families);
});
};
}]);
And the following main controller code:
app.controller('MainController', ['$scope', '$state', 'Cart', 'Library', function($scope, $state, Cart, Library) {
console.log('-> MainController');
// Serve the right font list depending on the page
$scope.fonts = $state.is('home.cart') ? Cart.getFonts() : Library.getFonts();
$scope.families = Library.getFamilies();
}]);
The problem is, that when the view requests the content of $scope.fonts, it's still empty.
How to update $scope.fonts and $scope.families when the loading is over?
I could use $scope.$watch but I'm sure there is a cleaner way to do it...
This really is what promises were made for. Your service should return a promise that is to be resolved. You could also simplify your service:
app.service('Library', ['$http', '$q', function($http, $q) {
var self = this;
self.families = [];
// Returns the family list
self.getFamilies = function() {
var deferred = $q.defer();
if(_.isEmpty(self.families)) {
$http.get(BACKEND_URL+'/families').success(function(data) {
var sorted = _.sortBy(data, function(item) { return item });
self.families = sorted;
deferred.resolve(self.families);
console.log('Library:: families updated', self.families);
});
} else {
deferred.resolve(self.families);
}
return deferred.promise;
};
}]);
And then in your controller, use the promises then method:
app.controller('MainController', ['$scope', '$state', 'Cart', 'Library', function($scope, $state, Cart, Library) {
console.log('-> MainController');
// Serve the right font list depending on the page
$scope.fonts = $state.is('home.cart') ? Cart.getFonts() : Library.getFonts();
Library.getFamilies().then(function(result) {
$scope.families = result;
});
}]);
This is untested because of the $http, but here is a demo using $timeout:
JSFiddle
Consider passing a callback function.
Service:
this.getFonts = function(callback) {
if(_.isEmpty(this.fonts)) this.updateFonts(callback);
return this.fonts;
};
this.updateFonts = function(callback) {
var self = this;
$http.get(BACKEND_URL+'/fonts').success(function(data) {
self.fonts = data;
console.log('Library:: fonts updated', self.fonts);
callback(data);
});
};
Controller:
Library.getFonts(function (data) { $scope.fonts = data; });
This could be tidied up a bit, since a callback eliminates the need for some of this code, but it'll serve as an example.
Thanks for all the answers! I ended up using a mix of callback and promise, as follow:
app.service('Library', function($http) {
// Returns the font list
this.getFonts = function(callback) {
if(_.isEmpty(self.fonts)) return self.updateFonts(callback);
else return callback(self.fonts);
};
// Update the font list
this.updateFonts = function(callback) {
return $http.get(BACKEND_URL+'/fonts').success(function(data) {
self.fonts = data;
callback(data);
});
};
});
And, in the controller:
app.controller('MainController', function(Library) {
Library.getFonts(function(fonts) { $scope.fonts = fonts });
});
I tried all your suggestions, but this is the best one working with the rest of my code.
In your this.getFonts function (and your other functions), you call the data from this, which points to the function instead of the controller scope you want. Try the following instead:
var self = this;
self.fonts = [];
self.families = [];
// ... some common CRUD functions here ...
// Returns the font list
self.getFonts = function() {
if(_.isEmpty(self.fonts)) self.updateFonts();
return self.fonts; // <-- self.fonts will point to the fonts you want
};
I would try wrapping your getScope and getFonts bodies that you are calling in a
$scope.$apply(function(){ ...body here... });
Make sure you declare self = this outside any functions.
Assign the call to the value you want to store the data in and then return it.
var self = this;
self.data = [];
this.updateFonts = function() {
self.fonts = $http.get(BACKEND_URL+'/fonts').success(function(data) {
return data.data
});
return self.fonts
};
Since you're using ui-router (i saw a $state). You can use a resolve in your state and return a promise.
Doc : https://github.com/angular-ui/ui-router/wiki
Exemple :
$stateProvider.state('myState', {
resolve:{
// Example using function with returned promise.
// This is the typical use case of resolve.
// You need to inject any services that you are
// using, e.g. $http in this example
promiseObj: function($http){
// $http returns a promise for the url data
return $http({method: 'GET', url: '/someUrl'});
}
},
controller: function($scope,promiseObj){
// You can be sure that promiseObj is ready to use!
$scope.items = promiseObj.data;
}
}
In your case you'll need to turn your this.getFonts and getFamilies into promises
this.getFonts = function(){
return $http.get(BACKEND_URL+'/fonts').success(function(data) {
self.fonts = data;
console.log('Library:: fonts updated', self.fonts);
});
}
There is many many way to do this, but in my opinion the resolve way is the best.
UPDATE:
Thanks for your reply!
I've rewritten my code:
(function () {
'use strict';
angular
.module('Services', []).factory('services', ['$http', function($http,services) {
function services($http) {
var serviceProvider = function () {
this.data = [];
this.errors = [];
}
var model = {
getInstance:function(){ return new serviceProvider(); }
}
serviceProvider.prototype.init = function(){//put some init stuff here
}
serviceProvider.prototype.getFromRESTServer = function(msg,callback){
return $http.jsonp("http://xxxxxxx/JSONEngine.php?callback=JSON_CALLBACK&action="+callback+"&"+msg);
}
return model;
}
}])
})();
And my controller is defined as:
var uniqueModelInstance = services.getInstance();
uniqueModelInstance.init();
uniqueModelInstance.getFromRESTServer("username="+$scope.username+"&password="+$scope.password,"register").success(function (data) {...}
Are they correct? Now I obtain "Cannot read property 'getInstance' of undefined".
Any suggestion?
Thanks in advance.
Giuseppe
I have an angular factory defined in this way:
services.factory('services', ['$http', function($http) {
var service = {};
return {
getFromRESTServer: function (msg,callback){
return $http.jsonp("http://myUrl/JSONEngine.php?callback=JSON_CALLBACK&action="+callback+"&"+msg);
}
}
}]);
and a controller with doLogin function:
home.controller('registrazioneTraduttoreCtrl', ['$scope', '$rootScope', '$window', 'services', '$location', 'customFactory',
function ($scope, $rootScope, $window, services, $location, customFactory) {
$scope.doLogin= function(username, password) {
services.getFromRESTServer("username="+username+"&password="+password,"login").
success(function (data) {
if(data.jsonError != null || data.errCode != null)
{
alert (data.errMsg);
}
else {
// DO STUFF...
}).error(function(data, status) {
console.error('Repos error', status, data);
})
.finally(function() {
console.log("finally finished repos");
});
}
}]);
The getFromRESTServer can be also executed by another function in another controller (there are 2 different Registration form in my html page and then they call doLogin function).
When I debug my application, the debugger skip from:
services.getFromRESTServer("username="+username+"&password="+password,"login") line (in doLogin function) to the end of getFromRESTServer funcion without going in and then re-execute the doLogin function with username and password NULL and now it enter in the core of getFromRESTServer function.
Any ideas?
Thanks in advance.
Giuseppe
You can do this by returning a new instance of any factory which is called. Look at this Plunker or try the following codes:
/**
* Non singleton factory example
*
* #name non-singleton-example
* #author Nils Gajsek <info#linslin.org>
*/
(function () {
//use strict -> ECMAScript5 error reporting
'use strict';
// ################################################ angularJS Module define // ####################################
/**
* DB service, part of app module
*/
angular
.module('app.model', []) // [-_-]
.factory('model', ['$http', model]);
/**
* Model factory wrapper
*
* #param {object} $http
*
* #returns self
*/
function model($http) {
// ################################################## Vars // ##############################################
var serviceProvider = function(){
/**
* Data store
* #type {Array}
*/
this.data = [];
/**
* Error store
* #type {Array}
*/
this.errors = [];
}
// ############################################### Functions // ############################################
/**
* Model instance provider handler
* This object is returned on the end of this object
*/
var model = {
getInstance:function(){ return new serviceProvider(); }
}
/**
* Model init function, provides
*/
serviceProvider.prototype.init = function(){
//put some init stuff here
}
/**
* Example function
*
* #returns {{this}}
*/
serviceProvider.prototype.someFunction = function(){
//do some stuff with model
}
//return model -> non-singleton model instance object
return model;
}
})();
This is how you receive it as unique instance.
var uniqueModelInstance = model.getInstance();
uniqueModelInstance.init();
Or better (but you need to return the instance itself by calling init() the function)
var uniqueModelInstance = model.getInstance().init();