I'm creating an app in Angular-Meteor, and i'd like create a few functions in my services which I can use in my controllers. However those functions use the $meteor.subscribe function, which queries the database and returns a call back. In my controller I want to call that function and bind that to the $scope, but then it returns undefined, because the call back hasn't returned anything yet. Is there a solution to keep the code in the service? Any tips?
An example:
Service
angular.module('GQ').service('AuthService', ['$meteor', function($meteor)
{
console.log('AuthService init')
this.getUserAuth = function() {
var user = {};
$meteor.subscribe('isAdmin').then(function(res){
//do database query...
//loop over returned values and do a check if query matches or not
// if it does match return true
// else return false
});
// then return the value
return user.isAdmin;
}
}]);
Controller
$scope.isAdmin = AuthService.getUserAuth();
console.log($scope.isAdmin) <--- undefined
You can use angular promises (official doc).
Example for your service:
this.getUserAuth = function() {
var deferred = $q.defer();
var user = {};
$meteor.subscribe('isAdmin').then(function(res, err){
// ....
// just an example
if (!res.isAdmin) deferred.reject('not an admin');
if (err) deferred.reject(err);
else deferred.resolve(res);
});
return deferred.promise;
}
Use in your controller:
AuthService.getUserAuth()
.then(function(res){
console.log(res); // the res from service
$scope.isAdmin = res; // is asynchronous, but angular updates the scope var
}, function(err){
// error handling here
});
Related
I'm running into an issue that I've managed to find a fix for but I was looking for some feedback to ensure I'm doing this the right way.
I have a function in a controller that makes a call to a service. If it fails, for whatever reason, I display an error message to the user.
//Controller
vm.login = function() {
vm.error = "";
Sessions.create(vm.user)
.then(function(result) {
$state.go("home");
})
.catch(function(result) {
vm.error = result.data.errors;
});
};
//Service
Sessions.create = function(data) {
return $http.post(API + "sessions", data)
.then(function(response) {
return response.data;
});
};
//View
.alert.alert-danger(role="alert" ng-show="vm.error") {{vm.error}}
The code above works exactly as intended. When it hits vm.error = result.data.errors that error message is correctly displayed to the user.
As I attempt to add some additional functionality to that service (localforage) I'm getting some weird behavior. Here's my new code:
//Controller
vm.newLogin = function() {
vm.error = "";
Sessions.login(vm.user)
.then(function(result) {
$state.go("home");
})
.catch(function(result) {
vm.error = result.data.errors;
$scope.$digest(); //NEED THIS?!?
});
};
//Services
Sessions.create = function(data) {
return $http.post(API + "sessions", data)
.then(function(response) {
return response.data;
});
};
Sessions.login = function(data) {
return new Promise(function(resolve, reject) {
Sessions.create(data)
.then(function(result) {
//do stuff
})
.then(function(result) {
return resolve(result);
})
.catch(function(err) {
return reject(err);
});
});
};
//View
.alert.alert-danger(role="alert" ng-show="vm.error") {{vm.error}}
With this code the error message that's set in newLogin isn't displayed to the user unless I add $scope.$digest(); after it. Am I doing something wrong here? I can log and see the error message in catch inside both login and newLogin. Why is digest only needed in the second version?
Using the browser Promise API is problematic. You should use the $q service to create promises. It is better integrated with the AngularJS framework. See AngularJS $q Service API Reference.
Usually, you don't call $digest() directly in controllers or in directives. Instead, you should call $apply() (typically from within a directive), which will force a $digest(). But in your case, the AngularJS $q service will take care of calling $apply(). For more information, see AngularJS $rootScope.scope API Reference -- $digest
Is there a way to return an HttpPromise (or something similar) to mimic a call to $http? I want to set a global variable that indicates whether the real HTTP request is made or whether a fake HttpPromise object is returned with fake data.
For example, I have a service that is similar to this:
angular
.module('myservice')
.factory('MyService', ['$http', function($http) {
return {
get : function(itemId) {
if (isInTestingMode) {
// return a promise obj that returns success and fake data
}
return $http.get("/myapp/items/" + itemId);
}
};
} ]);
And in my controller, I have a call to the aforementioned service that looks similar to this:
// Somewhere in my controller
MyService.get($scope.itemId)
.success(function(data) {
$scope.item = data;
})
.error(function(data, status, headers, config) {
$scope.notFound = true;
});
I'm trying to not change the controller code; I want the success and error chaining to still work when in my "isInTestMode".
Is it possible to fake an HttpPromise in the way that I described in the service?
Below is a revised edition of the "MyService" above (a snippet) containing a success and error on the promise object. But, how do I execute the success method?
return {
get : function(itemId) {
if (isInTestingMode) {
var promise = $.defer().promise;
// Mimicking $http.get's success
promise.success = function(fn) {
promise.then(function() {
fn({ itemId : "123", name : "ItemName"}, 200, {}, {});
});
return promise;
};
// Mimicking $http.get's error
promise.error = function(fn) {
promise.then(null, function(response) {
fn("Error", 404, {}, {});
});
return promise;
};
return promise;
}
return $http.get("/myapp/items/" + itemId);
}
}
Just use the deferred method of the $qservice
var fakeHttpCall = function(isSuccessful) {
var deferred = $q.defer()
if (isSuccessful === true) {
deferred.resolve("Successfully resolved the fake $http call")
}
else {
deferred.reject("Oh no! Something went terribly wrong in your fake $http call")
}
return deferred.promise
}
And then you can call your function like an $http promise (you have to customize whatever you want to put inside of it, of course).
fakeHttpCall(true).then(
function (data) {
// success callback
console.log(data)
},
function (err) {
// error callback
console.log(err)
})
I found that this post is similar to what I was asking.
However, I wanted a way to mock my service call so that fake data could be returned instead of issuing a true HTTP request call. The best way to handle this situation, for me, is to use angular's $httpBackend service. For example, to bypass a GET request to my "items" resource BUT to not bypass GETs of my partials/templates I would do something like this:
angular
.module('myApp', ['ngMockE2E'])
.run(['$httpBackend', function($httpBackend) {
$httpBackend
.whenGET(/^partials\/.+/)
.passThrough();
$httpBackend
.whenGET(/^\/myapp\/items\/.+/)
.respond({itemId : "123", name : "ItemName"});
}]);
See this documentation for more information on $httpBackend.
I finally found a way using jasmin. $httpBackend was no option for me, as there were also non-$http-methods I needed mock on the same service. I also think that the controller test needing to specify the url is not perfect as imho the controller and its test should not need to know about it.
Here is how it works:
beforeEach(inject(function ($controller, $rootScope, $q) {
scope = $rootScope.$new();
mockSvc = {
someFn: function () {
},
someHttpFn: function () {
}
};
// use jasmin to fake $http promise response
spyOn(mockSvc, 'someHttpFn').and.callFake(function () {
return {
success: function (callback) {
callback({
// some fake response
});
},
then: function(callback) {
callback({
// some fake response, you probably would want that to be
// the same as for success
});
},
error: function(callback){
callback({
// some fake response
});
}
}
});
MyCtrl = $controller('MyCtrl', {
$scope: scope,
MyActualSvc: mockSvc
});
}));
You can implement your FakeHttp class:
var FakeHttp = function (promise) {
this.promise = promise;
this.onSuccess = function(){};
this.onError = function(){};
this.premise.then(this.onSuccess, this.onError);
};
FakeHttp.prototype.success = function (callback) {
this.onSuccess = callback;
/**You need this to avoid calling previous tasks**/
this.promise.$$state.pending = null;
this.promise.then(this.onSucess, this.onError);
return this;
};
FakeHttp.prototype.error = function (callback) {
this.onError = callback;
/**You need this to avoid calling previous tasks**/
this.promise.$$state.pending = null;
this.promise.then(this.onSuccess, this.onError);
return this;
};
Then in your code, you would return a new fakeHttp out of the promise.
if(testingMode){
return new FakeHttp(promise);
};
The promise must be asynchronous, otherwise it won't work. For that you can use $timeout.
easy peasy!
You can do it using angular-mocks-async like so:
var app = ng.module( 'mockApp', [
'ngMockE2E',
'ngMockE2EAsync'
]);
app.run( [ '$httpBackend', '$q', function( $httpBackend, $q ) {
$httpBackend.whenAsync(
'GET',
new RegExp( 'http://api.example.com/user/.+$' )
).respond( function( method, url, data, config ) {
var re = /.*\/user\/(\w+)/;
var userId = parseInt(url.replace(re, '$1'), 10);
var response = $q.defer();
setTimeout( function() {
var data = {
userId: userId
};
response.resolve( [ 200, "mock response", data ] );
}, 1000 );
return response.promise;
});
}]);
I'm using a service to make user data available to various controllers in my Angular app. I'm stuck trying to figure out how to use the $http service to update a variable local to the service (in my case "this.users"). I've tried with and without promises. The server is responding correctly.
I've read several excellent articles for how to use $http within a service to update the scope of a controller. The best being this one: http://sravi-kiran.blogspot.com/2013/03/MovingAjaxCallsToACustomServiceInAngularJS.html. That does not help me though because it negates the benefits of using a service. Mainly, modifying the scope in one controller does not modify throughout the rest of the app.
Here is what I have thus far.
app.service('UserService', ['$http', function($http) {
this.users = [];
this.load = function() {
var promise = $http.get('users.json')
.success(function(data){
// this.users is undefined here
console.log(this.users);
}
};
promise.then(function() {
// this.users is undefined here
console.log('this.users');
});
}]);
Any help is greatly appreciated. Thank you.
Try using
var users = [];
rather than
this.users = [];
and see what
console.log(users);
outputs in each of those cases.
Your service is oddly defined, but if you have a return in it you can access it from any controller:
app.service('UserService', ['$http', function($http) {
var users = [];
this.load = function() {
var promise = $http.get('users.json')
.success(function(data){
// this.users is undefined here
console.log(users);
users = data.data;
}
};
return {
getUsers: function(){
return users;
}
}
}]);
so in your controller, you can use:
var myUsers = UserService.getUsers();
UPDATE to use a service correctly here, your service should return a promise and the promise should be accessed in the controller: Here's an example from another answer I gave
// your service should return a promise
app.service('PickerService', [$http', function($http) {
return {
getFiles: function(){
return $http.get('files.json'); // this returns a promise, the promise is not executed here
}
}
}]);
then in your controller do this:
PickerService.getFiles().then(function(returnValues){ // the promise is executed here as the return values are here
$scope.myDirectiveData = returnValues.data;
});
this does not have scope anymore where you are trying to use it do this instead:
app.service('UserService', [$http', function($http) {
var users = [];
this.load = function() {
var promise = $http.get('users.json')
.success(function(data){
console.log(users);
}
};
promise.then(function() {
console.log(users);
});
}]);
all local variables to a service should just be vars if you assign them to this as a property than they will be included every time the service is injected into a controller which is bad practice.
I think what your asking for is a solution along the lines of defining your service like this:
angular.module('app')
.service('User', function($http, $q) {
var users = null;
var deferred = $q.defer()
return {
getUsers: function() {
if(users) {
deferred.resolve(users);
} else {
$http.get('users.json');
.success(function(result) {
deferred.resolve(result);
})
.error(function(error) {
deferred.reject(error);
});
}
return deferred.promise;
}
};
});
Then in one Each controller you would have to do this:
angular.module('app')
.controller('ACtrl', function($scope, User) {
User.getUsers().then(function(users) {
// Same object that's in BCtrl
$scope.users = users;
});
});
angular.module('app')
.controller('BCtrl', function($scope, User) {
User.getUsers().then(function(users) {
// Same object that's in ACtrl
$scope.users = users;
});
});
NOTE: Because the deferred.promise the same promise passed to all controllers, executing deferred.resolve(users) in the future will cause all then success callbacks in each of your controllers to be called essentially overwriting the old users list.
All operations on the list will be noticed in all controllers because the users array is a shared object at that point. This will only handle updates to the user list/each individual user on the client side of your application. If you want to persist changes to the server, you're going to have to add other $http methods to your service to handle CRUD operations on a user. This can generally be tricky and I highly advise that you check out ngResource, which takes care of basic RESTful operations
I'm using an Angular factory that retrieves data from a feed and does some data manipulation on it.
I'd like to block my app from rendering the first view until this data preparation is done. My understanding is that I need to use promises for this, and then in a controller use .then to call functions that can be run as soon as the promise resolves.
From looking at examples I'm finding it very difficult to implement a promise in my factory. Specifically I'm not sure where to put the defers and resolves. Could anyone weigh in on what would be the best way to implement one?
Here is my working factory without promise:
angular.module('MyApp.DataHandler', []) // So Modular, much name
.factory('DataHandler', function ($rootScope, $state, StorageHandler) {
var obj = {
InitData : function() {
StorageHandler.defaultConfig = {clientName:'test_feed'};
StorageHandler.prepData = function(data) {
var i = 0;
var maps = StorageHandler.dataMap;
i = data.line_up.length;
while(i--) {
// Do loads of string manipulations here
}
return data;
}
// Check for localdata
if(typeof StorageHandler.handle('localdata.favorites') == 'undefined') {
StorageHandler.handle('localdata.favorites',[]);
}
},
};
return obj;
});
Here's what I tried from looking at examples:
angular.module('MyApp.DataHandler', []) // So Modular, much name
.factory('DataHandler', function ($rootScope, $q, $state, StorageHandler) {
var obj = {
InitData : function() {
var d = $q.defer(); // Set defer
StorageHandler.defaultConfig = {clientName:'test_feed'};
StorageHandler.prepData = function(data) {
var i = 0;
var maps = StorageHandler.dataMap;
i = data.line_up.length;
while(i--) {
// Do loads of string manipulations here
}
return data;
}
// Check for localdata
if(typeof StorageHandler.handle('localdata.favorites') == 'undefined') {
StorageHandler.handle('localdata.favorites',[]);
}
return d.promise; // Return promise
},
};
return obj;
});
But nothing is shown in console when I use this in my controller:
DataHandler.InitData()
.then(function () {
// Successful
console.log('success');
},
function () {
// failure
console.log('failure');
})
.then(function () {
// Like a Finally Clause
console.log('done');
});
Any thoughts?
Like Florian mentioned. Your asynchronous call is not obvious in the code you've shown.
Here is the gist of what you want:
angular.module("myApp",[]).factory("myFactory",function($http,$q){
return {
//$http.get returns a promise.
//which is latched onto and chained in the controller
initData: function(){
return $http.get("myurl").then(function(response){
var data = response.data;
//Do All your things...
return data;
},function(err){
//do stuff with the error..
return $q.reject(err);
//OR throw err;
//as mentioned below returning a new rejected promise is a slight anti-pattern,
//However, a practical use case could be that it would suppress logging,
//and allow specific throw/logging control where the service is implemented (controller)
});
}
}
}).controller("myCtrl",function(myFactory,$scope){
myFactory.initData().then(function(data){
$scope.myData = data;
},function(err){
//error loudly
$scope.error = err.message
})['finally'](function(){
//done.
});
});
I have a service that fetches some client data from my server:
app.factory('clientDataService', function ($http) {
var clientDataObject = {};
var cdsService = {
fetch: function (cid) {
//$http returns a promise, which has a then function, which also returns a promise
var promise = $http.get('/clients/stats/' + cid + '/').then(function (response) {
// The then function here is an opportunity to modify the response
console.log(response);
// The return value gets picked up by the then in the controller.
clientDataObject = {'data': response.data, 'currentClientID': cid};
return clientDataObject;
});
// Return the promise to the controller
return promise;
}
};
return cdsService;
});
Then in one controller I do:
//get stats
clientDataService.fetch($scope.id).then(function (response) {
$scope.client_data = {
'statistics': response.data
}
});
Which all works very well. However, I'm trying to do a watch from another controller on that service to update it's scope when the data changes, rather then having to re-kick off the http request:
$scope.$watch('clientDataService.clientDataObject', function (cid) {
alert(cid);
});
I'm just alerting for now, but it never ever triggers. When the page initially loads, it alerts "undefined". I have no errors in the console and all the $injects are fine, but it never seems to recognize that data has changed in the service. Am I doing something wrong in the watch?
Many thanks
Ben
clientDataService.clientDataObject is not part of your controller's scope, so you can't watch for changes on that object.
You need to inject the $rootScope into your service then broadcast the changes to the controllers scopes.
app.factory('clientDataService', function ($rootScope, $http) {
var clientDataObject = {};
var cdsService = {
fetch: function (cid) {
var promise = $http.get('/clients/stats/' + cid + '/').then(function (response) {
// The then function here is an opportunity to modify the response
console.log(response);
// The return value gets picked up by the then in the controller.
clientDataObject = {'data': response.data, 'currentClientID': cid};
$rootScope.$broadcast('UPDATE_CLIENT_DATA', clientDataObject);
return clientDataObject;
});
// Return the promise to the controller
return promise;
}
};
return cdsService;
});
Then in the controller you can listen for the change using:
$scope.$on('UPDATE_CLIENT_DATA', function ( event, clientDataObject ) { });
Another approach can be:
define new service
app.factory('DataSharingObject', function(){
return {};
}
include this new service in controller where we want to store the data
app.factory('clientDataService', function ($http, DataSharingObject) {
DataSharingObject.sharedata = ..assign it here
}
include this new service in controller where we want to access the data
app.factory('clientReceivingService', function ($http, DataSharingObject) {
..use it here... = DataSharingObject.sharedata
}