I'm attempting to call a service from within another service, then use the returned object to perform some operations. I keep running into a TypeError: getDefinitions is not a function error, however.
Below is my service is called, the service doing the calling, and my relevant controller code:
definitions.service.js:
'use strict';
angular.module('gameApp')
.factory('definitionsService', ['$resource',
function($resource) {
var base = '/api/definitions';
return $resource(base, {}, {
get: {method: 'GET', url: base}
});
}]);
utilities.service.js:
'use strict';
angular.module('gameApp')
.factory('utilitiesService', ['definitionsService', function(definitionsService) {
return {
description: description,
detail: detail,
severity: severity,
};
function description(account) {
var key = angular.isDefined(getDefinitions().ABC[account.code]) ? account.code : '-';
return getDefinitions().IDV[key].description;
}
function detail(account) {
var key = angular.isDefined(getDefinitions().ABC[account.code]) ? account.code : '-';
return getDefinitions().IDV[key].detail;
}
function severity(account) {
var key = angular.isDefined(getDefinitions().ABC[account.code]) ? account.code : '-';
return getDefinitions().IDV[key].severity;
}
var getDefinitions = function() {
definitionsService.get().$promise.then(function(data) {
return data;
});
};
}]);
controller.js:
'use strict';
angular.module('gameApp')
.controller('AccountsController', AccountsController);
AccountsController.$inject = ['$routeParams', 'customersService', 'utilitiesService'];
function AccountsController($routeParams, playersService, utilitiesService) {
var vm = this;
var playerId = $routeParams.playerId;
var getAccounts = function() {
playersService.getAccounts({
playerId: playerId
}).$promise.then(function(accounts) {
for (var i = 0; i < accounts.length; i++) {
if (angular.isDefined(accounts[i].secCode)) {
accounts[i].code = accounts[i].secCode;
accounts[i].severity = utilitiesService.severity(accounts[i]);
accounts[i].detail = utilitiesService.detail(accounts[i]);
accounts[i].description = utilitiesService.description(accounts[i]);
}
}
vm.accounts = accounts;
});
};
var init = function() {
getAccounts();
};
init();
}
Currently your service returns before your variable gets defined. That means the definition is never reached. So it is declared, as the function executes, but is undefined. Just move your variable definition to the top.
This will only prevent the definition error. Another problem is that your getDefinitions function doesn't return anything but you're calling a property on it. One solution I can think of is using a callback, that gets executed when your data is loaded:
angular.module('gameApp')
.factory('utilitiesService', ['definitionsService', function(definitionsService) {
var data;
reload();
var utils = {
description: description,
detail: detail,
severity: severity,
reload: reload,
loaded: null
};
return utils;
function reload() {
definitionsService.get().$promise.then(function(data) {
data = data;
if (utils.loaded && typeof utils.loaded === "function") {
utils.loaded();
}
});
}
function description(account) {
var key = angular.isDefined(data.ABC[account.code]) ? account.code : '-';
return data.IDV[key].description;
}
}]);
Then in your controller you could use the service like this:
utilitiesService.loaded(function(){
accounts[i].description = utilitiesService.description(accounts[i]);
})
old question but still relevant. To expand on Florian Gl's answer above if you have a service with multiple functions and one or more of those functions requires a "pre-service" function to be called for example to load some resource data in like configuration information move that service call to the top, outside of the nested function (in this case below I am dealing with the promise scenario in JavaScript):
angular.module('gameApp')
.factory('utilitiesService', ['definitionsService', function(definitionsService) {
var myFirstConfigValue = '';
// call any all services here, set the variables first
configurationService.GetConfigValue('FirstConfg')
.then(function (response) {
// set the local scope variable here
myFirstConfigValue = response;
},
function() { });
function myTestFunction() {
// make an ajax call or something
// use the locally set variable here
ajaxService.functionOneTwo(myFirstConfigValue)
.then(response) {
// handle the response
},
function(err) {
// do something with the error
});
}
}]);
Key point to note here is that if you need to load in some data you do that first outside of any other functions inside your service (e.g. you want to load some JSON data).
Related
I have a separate model and a controller for a teachers list.
My teacherModel.js is:
app.factory('Teacher', [function() {
function Teacher(teacher) {
// constructor
};
Teacher.prototype = {
setTeacher: function(teacher) {
angular.extend(this, teacher);
},
getAllTeachers: function(callback) {
var scope = this;
var ref = firebase.database().ref('/xxx/teachers');
ref.once('value').then(function(snapshot) {
teachersList = snapshot.val();
scope.setTeacher(teachersList);
// THERE'S A PROBLEM HERE...
// I'm trying to pass this callback from the controller:
callback;
});
}
};
return Teacher;
}]);
Now from my controller I call the getAllTeachers() method with a callback function:
app.controller('teacherMainCtrl', ['$scope', 'Teacher', function($scope, Teacher){
var teacher = new Teacher()
teacher.getAllTeachers(function() {
$scope.teachers = teacher;
console.log($scope.teachers);
});
}]);
Problem is console.log($scope.teachers); is not logging anything to the console. I don't think the callback is being executed at all.
Can someone help me to figure out what I'm doing wrong or suggest a better way to add functionality to the model data from controller after the data is asynchronously retrieved from firebase? Thanks.
You can leverage the fact that once returns a firebase promise so you can alter your code to the following:
app.factory('Teacher', [function() {
function Teacher(teacher) {
// constructor
};
Teacher.prototype = {
setTeacher: function(teacher) {
angular.extend(this, teacher);
},
getAllTeachers: function() {
var scope = this;
var ref = firebase.database().ref('/xxx/teachers');
return ref.once('value').then(function(snapshot) {
return snapshot.val();
});
}
};
return Teacher;
}]);
This would behave similarly to any $http request where it returns a promise. Now, in your controller, you can then call your getAllTeachers() like so:
app.controller('teacherMainCtrl', ['$scope', 'Teacher', function($scope, Teacher){
var teacher = new Teacher()
teacher.getAllTeachers().then(function (snapshotValues) {
// What you returned in the promise above is populated in snapshotValues here
$scope.teachers = snapshotValues;
});
}]);
Update
If you want to use the $q service for your particular scenario, you can do the following:
app.factory('Teacher', ['$q', function($q) {
function Teacher(teacher) {
// constructor
};
Teacher.prototype = {
setTeacher: function(teacher) {
angular.extend(this, teacher);
},
getAllTeachers: function() {
var defer = $q.defer();
var scope = this;
var ref = firebase.database().ref('/xxx/teachers');
ref.once('value').then(function(snapshot) {
var val = snapshot.val();
// Transform your data any way you want.
// Whatever you pass into resolve() will be available as a parameter in the subsequent then()
defer.resolve(val);
});
return defer.promise;
}
};
return Teacher;
}]);
Using the method would still be the same. You simply just call then()
teacher.getAllTeachers()
.then(function (whatYouPassedInResolve) {
});
Another thing to note is that in the getAllTeachers method inside of your factory, I did not handle any error cases. That would be achieved by rejecting the promise with defer.reject(objectToSendBack). You pass in any data you want accessible when you deem that call a failure.
Just pass in a function for the second parameter to the `then(successCallback, errorCallback) to handle any rejected promises.
I think you are not calling the callback actually, use callback()
app.factory('Teacher', [function() {
function Teacher(teacher) {
// constructor
};
Teacher.prototype = {
setTeacher: function(teacher) {
angular.extend(this, teacher);
},
getAllTeachers: function(callback) {
var scope = this;
var ref = firebase.database().ref('/xxx/teachers');
ref.once('value').then(function(snapshot) {
teachersList = snapshot.val();
scope.setTeacher(teachersList);
// THERE'S A PROBLEM HERE...
// Try this
callback();
});
}
};
return Teacher;
}]);
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.
I am very new to this Angular component communication. I am using Angular 1.5.X version and I am using factory to share data between components. I am facing one issue where Async value of Service Variable refreshes after certain time.
I understand one solution is to set watcher on non scope variable but I think I am missing something important here. Can you guys please share views?
This is Service.js code
Service.$inject = ['$http','$q'];
function Service($http,$q) {
this.$http = $http;
this.$q = $q;
};
Service.prototype.getTileCount = 0;
Service.prototype.getTileData = function(Url){
var deferred = this.$q.defer();
this.$http.get(Url)
.success(function(response){
Service.prototype.getTileCount = response.data.length;
console.log('data length :', Service.prototype.getTileCount);
deferred.resolve(response);
});
return deferred.promise;
};
This is component 1 controller code
function Component1Controller(Service) {
this.tileData ={};
var self = this;
var promise = Service.getTileData(this.sourceUrl);
promise.then(function(data) {
self.tileData = data;
Service.getTileCount = data.length;
console.log('This is tileData : '+ Service.getTileCount);
});
};
This is component 2 controller code
function Component2Controller(Service) {
var self = this;
console.log(Service.getTileCount);
// getting getTileCount = 0; After setting timeout function of 5 second I am able to get getTileCount value
};
The thing is that Service.getTileCount is updated asynchronously, that's why it's 0 at first and then at some point it changes. I would recommend you to simplify your service and always work with getTileData method, which would be a single source of data. The implementation would also become simpler:
function Service($http, $q) {
this._tileData = null;
this.$http = $http;
this.$q = $q;
}
Service.prototype.getTileData = function(Url) {
if (!this._tileData) {
this._tileData = this.$http.get(Url).then(function(response) {
return response.data;
}.bind(this));
}
return this._tileData;
};
Note, how it caches tiles response in "private" _tileData property. Now you can always rely on getTileData method which will return data no matter when you call it:
function Component1Controller(Service) {
this.tileData = {};
var self = this;
Service.getTileData(this.sourceUrl).then(function(data) {
self.tileData = data;
console.log('This is tileData:', self.tileData.length);
});
};
function Component2Controller(Service) {
var self = this;
Service.getTileData(this.sourceUrl).then(function(data) {
console.log('tile count', data.length);
});
};
In this case Service.getTileCount is not needed anymore.
Demo: http://plnkr.co/edit/3zE6VL4emXaLRx2nCRih?p=info
I have a really serious problem, I'm updating, editing, deleting data, and the two-way data binding is not working.
This is one of my controllers:
'use strict';
var EventController = function($timeout, $scope, $state, EventModel) {
this.$timeout = $timeout;
this.$scope = $scope;
this.$state = $state;
this.EventModel = EventModel;
/**
* When the page is requested, retrieve all the data.
*
*/
this.retrieve();
};
EventController.prototype = {
create: function(event) {
var that = this;
this.EventModel.Model.insert(event)
.then(function() {
that.refresh();
});
},
retrieve: function() {
var that = this;
this.EventModel.Model.find()
.then(function(result) {
that.$scope.events = result;
});
},
one: function(id) {
var that = this;
this.EventModel.Model.one(id)
.then(function(result) {
that.$scope.event = result;
});
},
update: function(id, event, state) {
if (state !== undefined) {
event.is_active = state;
}
var that = this;
this.EventModel.Model.update(id, event)
.then(function() {
that.refresh();
});
},
delete: function(id) {
var check = $('[data-controller-input]:checked');
var that = this;
$.each(check, function() {
var target = $(this);
var id = target.prop('id');
that.EventModel.Model.remove(id)
.then(function() {
that.refresh();
});
});
},
clear: function() {
this.$scope.event = angular.copy(this.$scope.initial);
},
refresh: function() {
this.$state.go(this.$state.current, {}, {reload: true});
}
};
angular
.module('adminApp')
.controller('EventController',
[
'$timeout',
'$scope',
'$state',
'EventModel',
EventController
]
);
In the create, update and delete methods I need to update the HTML without refreshing the page, I already tried using, $scope.apply, $scope.digest, $timeout after the result came, but not happens in the HTML.
If I try $scope.apply and $scope.digest the error will be:
Prevent error $digest already in progress when calling $scope.$apply()
So I was trying to wrap the $scope.$apply or $digest with the $timeout, same result, nothing happens.
Thanks.
First of all, your refresh method will never update your controller.it will simply fail just because this.$state.current won't be able to resolve any url ,template or controller.
And this is the main reason you are not able to see updated data ,just check your console you might be getting Error: Cannot transition to abstract state '[object Object]' error.
Update : I have create a plnkr.as i don't have access to event model code i simply removed it and try to create the same scenario.
http://plnkr.co/edit/RsI3TgKwcjGEXcTMKoQR?p=preview
see if this can help you
I am not sure, but try using the following function which checks the current phase before executing your function. It may solve the issue.
$scope.safeApply = function(fn) {
var phase = this.$root.$$phase;
if(phase == '$apply' || phase == '$digest') {
if(fn && (typeof(fn) === 'function')) {
fn();
}
} else {
this.$apply(fn);
}
};
Usage:
$scope.safeApply(function() {
//Your lines
});
I've just started using AngularJS and I love it.
However - I have a need to save an item to my database using $resource and then get back and object containing the values of the newly created item in the database (especially the database-assigned ID).
I've found a few articles describing this - but none of them seems to work for me :(
I have a very simple setup:
var app = angular.module("todoApp", ['ngResource', 'ngAnimate']);
app.factory("TodoFactory", function ($resource) {
return $resource('.../api/todo/:id', { id: '#id' }, { update: { method: 'PUT' }});
});
var todoController = app.controller("TodoController", function ($scope, TodoFactory) {
$scope.todos = [];
init();
function init() {
$scope.todos = TodoFactory.query();
}
$scope.addTodo = function () {
TodoFactory.save($scope.item, function () {
// Success
console.log($scope.item); // <--- HERE'S MY PROBLEM
$scope.todos.push($scope.item);
$scope.item = {};
},
function () {
// Error
});
};
But when I call TodoFactory.save, the $scope.item does not contain the Id-property from the database - only the values it had upon calling save.
How can I get my setup to return the updated object with all the database-generated values?
If somebody could point me in the right direction it would be much appreciated :)
Update: I just went over the source for the API I've been supplied - the save-method doesn't update the object that's inserted.
After I fixed this "minor" issue, peaceman's example worked like a charm.
Sorry for the inconvenience everybody - but thank you very much for the responses! :)
The save method of the TodoFactory won't update the $scope.item, but instead calls the callback function with the saved object as a parameter, that contains the new id.
So you have to replace
$scope.addTodo = function () {
TodoFactory.save($scope.item, function () {
// Success
console.log($scope.item); // <--- HERE'S MY PROBLEM
$scope.todos.push($scope.item);
$scope.item = {};
},
function () {
// Error
});
};
with
$scope.addTodo = function () {
TodoFactory.save($scope.item, function (savedTodo) {
// Success
console.log(savedTodo);
$scope.todos.push(savedTodo);
$scope.item = {};
},
function () {
// Error
});
};
This behaviour is documented at ngResource.$resource
This should solve the problem.
$scope.addTodo = function () {
// ADD IN A VARIABLE FOR THE RESPONSE DATA AS THE PARAMETER TO YOUR SUCCESS CALLBACK FN
TodoFactory.save($scope.item, function (responseItem) {
// Success
console.dir(responseItem); // <--- AND THEN USE IT
$scope.todos.push(responseItem);
$scope.item = {};
},
function () {
// Error
});
};