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;
}]);
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 have a json-file defined and I am trying to load in one of my controllers. I am using a factory to fetch the data:
.factory('myService', function($http) {
var all_data = [];
return {
getAllData: function(){
return $http.get('js/data/all_data.json').then(function(data) {
all_data = data;
return all_data ;
});
}
}
})
Later in my controller I call getAllData() in a loadData()-function:
.controller('QuizCtrl',['$scope','$state','$http','myService',function($scope,$state,$http,myService){
// $scope.myData = []; <-- this makes the app freeze and not respond anymore
$scope.loadData = function(){
myService.getAllData().then(function(all_data){
$scope.myData = all_data.data.all_data;
alert($scope.myData);
});
}
$scope.loadData();
$scope.another_var = $scope.myData;
}])
As you can see first of all I am also calling loadData(). While debugging inside the function (see alert()) I can clearly see how the json has been loaded and applied to the $scope.myData variable.
Once I try to assign the variable to another variable (see $scope.another_var) myData is 'undefined'.
What I tried was defining $scope.myData before the $scope.loadData() call (see comment in code). Unfortunately, this simple variable declaration makes my app freeze completely. I have not found the reason for this yet. Also, I am not sure if it is related to my overall problem.
So what have I missed? Why am I not able to store my "http get" result in my controller's $scope?
EDIT: So in my case, I need the data to be there before the current Controller is even used. Would it be a legit option to put all the code which is executed within the controller into the .then-chain of the promise?
It's because your HTTP request is an asyncronous function while the assignment $scope.another_var = $scope.myData; is syncronous.
Basically what's going on is that when your QuizCtrl controller is loaded, it finishes the statement $scope.another_var = $scope.myData; before it finishes the http request of getAllData(). What you've got is a race condition.
If you want to change the value of another_var move it within your async callback:
$scope.loadData = function(){
myService.getAllData().then(function(all_data){
$scope.myData = all_data.data.all_data;
alert($scope.myData);
// because now $scope.myData is available this assignment will work:
$scope.another_var = $scope.myData;
});
}
$scope.loadData();
Hope this helps.
If you need to udpate a different value based on the value that is already on scope, you could observe the value for changes and update accordingly.
here is what you could do.
var app = angular.module("sampleApp", []);
app.controller("sampleController", ["$scope", "sampleService",
function($scope, sampleService) {
sampleService.sampleMethod(1).then(function(value) {
$scope.value = value;
$scope.$digest();
}, function(error) {});
$scope.$watch(function() {
return $scope.value;
}, function(newValue, oldValue) {
//Default Value when the dependant value is not available
newValue = newValue || 0;
$scope.myNewValue = newValue * 10;
});
}
]);
app.service("sampleService", function() {
this.sampleMethod = function(value) {
var promise = new Promise(function(resolve, reject) {
setTimeout(function() {
value = value * 2;
resolve(value);
}, 1000);
});
return promise;
};
});
<script src="https://cdnjs.cloudflare.com/ajax/libs/angular.js/1.5.8/angular.min.js"></script>
<div ng-app="sampleApp">
<div ng-controller="sampleController">
<div>Value: {{value}}</div>
<div>Cloned Value : {{myNewValue}}
</div>
</div>
</div>
You are missing a promiss $q
take this method for instance:
.factory('myService', function($http,$q) {
var all_data = [];
return {
getAllData: function () {
var d = $q.defer();
$http.get('js/data/all_data.json')
.success(function (response) {
d.resolve(response);
});
return d.promise;
}
}
})
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'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).
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.