Angular Best Practice: Data at start-up in a service - angularjs

I want to create an angular service that grabs data at "start-up" and builds up an array that will not change for the lifetime of the application. I don't want to return the promise of the http request. Caching the result would still incurs the cost of the 'someExpensiveFunctionCall'. What's the best way to handle this situation that can guarantee that the service will have the result populated and can be used in a controller and/or another service.
angular.module('app')
.service('MyService', function($http, defaults) {
var result = [];
$http.get('/some/url').then(function(data) {
if(angular.isDefined(data)) {
_.forEach(data, function(item) {
result.push(someExpensiveFunctionCall(item.value));
});
} else {
result = angular.copy(defaults.defaultResult);
}
});
return {
result: result
};
})
.controller(function(MyService) {
var someData = MyService.result;
})
.service(function(MyService) {
var someData = MyService.result;
});

I don't want to return the promise of the http request.
It seems like you want to do something that is asynchronous; synchronously. This can't work (reliably) without some sort of callback, promise, sync XHR, or other mechanism.
Knowing that we can evaluate some options. (in order of most practical)
Use your routers resolve property. This will complete some promise returning operation before instantiating your controller. It also (in ui-router and ngRoute) means the result of the promise will be injected into the controller under the specified key name.
Use the promise.
(aka #1 manually)
.factory('MyService',function($http,defaults){
var ret = {result: []};
ret.promise = $http.get('url').then(function(data){
if(angular.isDefined(data)){
_.forEach(data,function(item){
ret.result.push(someExpensiveFunctionCall(item.value));
});
}else{
_.forEach(angular.copy(defaults.defaultResult),function(val){
ret.result.push(val);
});
}
return ret;
});
return ret;
})
.controller('MyCtrl',function(MyService){
MyService.promise.then(myCtrl);
function myCtrl(){
var result = MyService.result
}
})
Lazy Evaluation. --> this is a little slow in angular due to dirty checking. But essentially means that you don't immediately manipulate/read result inside the body of a controller or service. But instead, use the view bindings, filters and interactions to call scope functions that act on the data. Since the resolution of a promise will invoke a digest cycle. Your view will render when the data arrives.
There was a bug in your code if the data happens to be undefined. You are de-referencing result by setting it to a copy of the defaults.
So all references to MyService.result will always be an empty array and never the default value.
In order to maintain the reference I'd loop over the defaults and add them to result with result.push.
Hope this helps!

Related

how to return a value from a ajax promise

I have a method in my angular 1.5 controller, as shown below but I wanted to refactor the ajax call into the factory itself but I'm having problems with promises.. I'm trying to get to a point where in my controller I can just call the method like I've shown below. Is this possible? I'm trying to avoid having the ...success(function(...) in the controller code.
Any help much appreciated.
Trying to move to
vm.member = someFactory.getMember(vm.id);
Existing working controller code
vm.myMethod = myMethod;
...
function myMethod() {
someFactory.getMember(vm.id).success(function(response) {
vm.member = response;
});
}
When I move the getMethod line into the factory the response is populated obviously but as soon as I come back to the controller, even with the return value from the factory being the response the result is undefined. I know this is because of promises but is there a design pattern I'm missing or a clean way of doing this. Using my currently approach my controller is littered with .success(function()...)
Many thanks!
The procedure is called promise unwrapping.
Besides the fact that success is deprecated and should be replaced with then,
someFactory.getMember(vm.id).then(function(response) {
var data = res.data;
...
});
it is totally ok to have this in controller.
The alternative to this pattern is to return self-filling object (something that ngResource $resource does):
function getMember(...) {
var data = {};
$http(...).then(function (response) {
// considering that response data is JSON object,
// it can replace existing data object
angular.copy(data, response.data);
});
return data;
}
In this case controller can get a reference to the object instantly, and the bindings of object properties {{ vm.member.someProperty }} will be updated in view on response.
The pattern is limited to objects (and arrays), scalar values should be wrapped with objects.

AngularJs: Does not update the model after the modifying it through $http transform

I need to transform objects coming from $http call to an api. My code adds some fields (functions) to the object coming from the api, here the constructor of this object :
(function () {
window.TransformedObject = function (obj) {
var self = this;
self = {};
if (obj) {
self = angular.copy(obj);
}
self.hasChanged = function () {
// return true or false if the object has changed
}
return self;
}
}());
The $http transform code looks like this :
$http({
url: 'api/...',
method: 'GET',
transformResponse: function(value) {
return new TransformedObject(JSON.parse(value));
})
}).success(function(data){
vm.obj = angular.copy(data);
});
Note that the value in the transformResponse callback is stringified, and need to be parsed to get the object
All this is working fine, suppose the object coming from the api contains a key called title, doing obj.title = 'some title' will update the object.
The problem :
Binding the title field with an input tag will not update the object if the change is coming from the view.
I use a regular ng-model to do it:
<input type="text" placeholder="Title" ng-model="vm.obj.title"/>
even using $rootScope.$watch will never be triggered if the change is coming from the view aka the input tag.
$rootScope.$watch(function () {
return vm.obj;
}, function () {
console.log('watch');
// this log will never appear in the console
});
Am I doing something wrong, why transforming the object coming from the api is breaking angulars binding ???
Thanks.
http://www.bennadel.com/blog/2605-scope-evalasync-vs-timeout-in-angularjs.htm
Sometimes, in an AngularJS application, you have to explicitly tell
AngularJS when to initiate it's $digest() lifecycle (for dirty-data
checking). This requirement is typically contained within a Directive;
but, it may also be in an asynchronous Service. Most of the time, this
can be easily accomplished with the $scope.$apply() method. However,
some of the time, you have to defer the $apply() invocation because it
may or may not conflict with an already-running $digest phase. In
those cases, you can use the $timeout() service; but, I'm starting to
think that the $scope.$evalAsync() method is a better option.
...
Up until now, my approach to deferred-$digest-invocation was to
replace the $scope.$apply() call with the $timeout() service (which
implicitly calls $apply() after a delay). But, yesterday, I discovered
the $scope.$evalAsync() method. Both of these accomplish the same
thing - they defer expression-evaluation until a later point in time.
But, the $scope.$evalAsync() is likely to execute in the same tick of
the JavaScript event loop.

How to pass already-fetched data to a controller when it's expecting a promise

I have a service in Angular that fetches a list of querys from a database table. The user selects one and moves on to another view to work with it. When the user clicks back to the first view though, I'd like to avoid re-fetching the query list. The query list is stored in the service but I'm having a hard time handing it back to the controller given that my fetch routine uses .then and promises.
app.service('queryService', function ($http)
{
var querys = new Object();
this.loadQueryList = function()
{
if (querys!=null)
{
//how to return the querys list here? caller expecting a promise
}
var promise = $http.post("php/datapump.php", { action: "FETCH", item: "QUERYS", id1: null, id2: null})
.then(function (response)
{
querys=response.data;
return querys;
});
return promise; // Return the promise to the controller
};
});
In the controller initialization the service is called with:
queryService.loadQueryList().then(function(d)
{
$scope.querys = d;
$scope.selectedquery=queryService.getSelectedQuery();
});
Or is there a better method altogether? I can imagine setting some flag in the service to get around this but it seems ugly. I simply want to avoid fetching the data a 2nd time.
You could create your own promise and resolve it immediately. This is much nicer than setting a flag and returning some cached data directly because it keeps your service interface consistent - that method always returns a promise.
var deferred = $q.defer();
deferred.resolve(yourdata);
return deferred.promise;
You will obviously need to inject $q into your service.

Should services expose their asynchronicity?

I'm writing a service that will retrieve data asynchronously ($http or $resource). I can hide the fact that it is asynchronous by returning an array that will initially be empty, but that will eventually get populated:
.factory('NewsfeedService1', ['$http', function($http) {
var posts = [];
var server_queried = false;
return {
posts: function() {
if(!server_queried) {
$http.get('json1.txt').success(
function(data) {
server_queried = true;
angular.copy(data, posts);
});
}
return posts;
}
};
}])
.controller('Ctrl1', ['$scope','NewsfeedService1',
function($scope, NewsfeedService1) {
$scope.posts = NewsfeedService1.posts();
}])
Or I can expose the asynchronicity by returning a promise:
.factory('NewsfeedService2', ['$http', function($http) {
var posts = [];
var server_queried = false;
var promise;
return {
posts_async: function() {
if(!promise || !server_queried) {
promise = $http.get('json2.txt').then(
function(response) {
server_queried = true;
posts = response.data;
return posts;
});
}
return promise;
}
};
}])
.controller('Ctrl2', ['$scope','NewsfeedService2',
function($scope, NewsfeedService2) {
NewsfeedService2.posts_async().then(
function(posts) {
$scope.posts = posts;
});
// or take advantage of the fact that $q promises are
// recognized by Angular's templating engine:
// (note that Peter and Pawel's AngularJS book recommends against this, p. 100)
$scope.posts2 = NewsfeedService2.posts_async();
}]);
(Plunker - if someone wants to play around with the above two implementations.)
One potential advantage of exposing the asychronicity would be that I can deal with errors in the controller by adding an error handler to the then() method. However, I'll likely be catching and dealing with $http errors in an application-wide interceptor.
So, when should a service's asynchronicity be exposed?
My guess is that you'll find people on both sides of this fence. Personally, I feel that you should always expose the asynchronicity of a library or function (or more correctly: I feel that you should never hide the asynchronicity of a library or function). The main reason is transparency; for example, will this work?
app.controller('MyController', function(NewsfeedService) {
$scope.posts = NewsfeedService.posts();
doSomethingWithPosts($scope.posts); // <-- will this work?
});
If you're using the first method (e.g. $resource), it won't, even though $scope.posts is technically an array. If doSomethingWithPosts has its own asynchronous operations, you could end up with a race condition. Instead, you have to use asynchronous code anyway:
app.controller('MyController', function(NewsfeedService) {
$scope.posts = NewsfeedService.posts(function() {
doSomethingWithPosts($scope.posts);
});
});
(Of course, you can make the callback accept the posts as an argument, but I still think it's confusing and non-standard.)
Luckily, we have promises, and the very purpose of a promise is to represent the future value of an operation. Furthermore, since promises created with Angular's $q libraries can be bound to views, there's nothing wrong with this:
app.controller('MyController', function(NewsfeedService) {
$scope.posts = NewsfeedService.posts();
// $scope.posts is a promise, but when it resolves
// the AngularJS view will work as intended.
});
[Update: you can no longer bind promises directly to the view; you must wait for the promise to be resolved and assign a scope property manually.]
As an aside, Restangular, a popular alternative to $resource, uses promises, and AngularJS' own $resource will be supporting them in 1.2 (they may already support them in the latest 1.1.x's).
I would always go with async option since i don't like hiding the async nature of the underlying framework.
The sync version may look more clean while consuming it, but it inadvertently leads to bug where the developer does not realize that the call is async in nature and tries to access data after making a call.
SO is filled with questions where people make this mistake with $resource considering it sync in nature, and expecting a response. $resource also takes similar approach to option 1, where results are filled after the call is complete, but still $resource exposes a success and failure function.
AngularJS tries to hide the complexities of async calls if promises are returned, so binding directly to a promise feels like one is doing a sync call.
I say no, because it makes it harder to work with multiple services built this way. With promises, you can use $q.all() to make multiple request and respond when all of them complete, or you can chain operations together by passing the promise around.
There would be no intuitive way to do this for the synchronous style service.

AngularJS : Where to use promises?

I saw some examples of Facebook Login services that were using promises to access FB Graph API.
Example #1:
this.api = function(item) {
var deferred = $q.defer();
if (item) {
facebook.FB.api('/' + item, function (result) {
$rootScope.$apply(function () {
if (angular.isUndefined(result.error)) {
deferred.resolve(result);
} else {
deferred.reject(result.error);
}
});
});
}
return deferred.promise;
}
And services that used "$scope.$digest() // Manual scope evaluation" when got the response
Example #2:
angular.module('HomePageModule', []).factory('facebookConnect', function() {
return new function() {
this.askFacebookForAuthentication = function(fail, success) {
FB.login(function(response) {
if (response.authResponse) {
FB.api('/me', success);
} else {
fail('User cancelled login or did not fully authorize.');
}
});
}
}
});
function ConnectCtrl(facebookConnect, $scope, $resource) {
$scope.user = {}
$scope.error = null;
$scope.registerWithFacebook = function() {
facebookConnect.askFacebookForAuthentication(
function(reason) { // fail
$scope.error = reason;
}, function(user) { // success
$scope.user = user
$scope.$digest() // Manual scope evaluation
});
}
}
JSFiddle
The questions are:
What is the difference in the examples above?
What are the reasons and cases to use $q service?
And how does it work?
This is not going to be a complete answer to your question, but hopefully this will help you and others when you try to read the documentation on the $q service. It took me a while to understand it.
Let's set aside AngularJS for a moment and just consider the Facebook API calls. Both the API calls use a callback mechanism to notify the caller when the response from Facebook is available:
facebook.FB.api('/' + item, function (result) {
if (result.error) {
// handle error
} else {
// handle success
}
});
// program continues while request is pending
...
This is a standard pattern for handling asynchronous operations in JavaScript and other languages.
One big problem with this pattern arises when you need to perform a sequence of asynchronous operations, where each successive operation depends on the result of the previous operation. That's what this code is doing:
FB.login(function(response) {
if (response.authResponse) {
FB.api('/me', success);
} else {
fail('User cancelled login or did not fully authorize.');
}
});
First it tries to log in, and then only after verifying that the login was successful does it make the request to the Graph API.
Even in this case, which is only chaining together two operations, things start to get messy. The method askFacebookForAuthentication accepts a callback for failure and success, but what happens when FB.login succeeds but FB.api fails? This method always invokes the success callback regardless of the result of the FB.api method.
Now imagine that you're trying to code a robust sequence of three or more asynchronous operations, in a way that properly handles errors at each step and will be legible to anyone else or even to you after a few weeks. Possible, but it's very easy to just keep nesting those callbacks and lose track of errors along the way.
Now, let's set aside the Facebook API for a moment and just consider the Angular Promises API, as implemented by the $q service. The pattern implemented by this service is an attempt to turn asynchronous programming back into something resembling a linear series of simple statements, with the ability to 'throw' an error at any step of the way and handle it at the end, semantically similar to the familiar try/catch block.
Consider this contrived example. Say we have two functions, where the second function consumes the result of the first one:
var firstFn = function(param) {
// do something with param
return 'firstResult';
};
var secondFn = function(param) {
// do something with param
return 'secondResult';
};
secondFn(firstFn());
Now imagine that firstFn and secondFn both take a long time to complete, so we want to process this sequence asynchronously. First we create a new deferred object, which represents a chain of operations:
var deferred = $q.defer();
var promise = deferred.promise;
The promise property represents the eventual result of the chain. If you log a promise immediately after creating it, you'll see that it is just an empty object ({}). Nothing to see yet, move right along.
So far our promise only represents the starting point in the chain. Now let's add our two operations:
promise = promise.then(firstFn).then(secondFn);
The then method adds a step to the chain and then returns a new promise representing the eventual result of the extended chain. You can add as many steps as you like.
So far, we have set up our chain of functions, but nothing has actually happened. You get things started by calling deferred.resolve, specifying the initial value you want to pass to the first actual step in the chain:
deferred.resolve('initial value');
And then...still nothing happens. To ensure that model changes are properly observed, Angular doesn't actually call the first step in the chain until the next time $apply is called:
deferred.resolve('initial value');
$rootScope.$apply();
// or
$rootScope.$apply(function() {
deferred.resolve('initial value');
});
So what about error handling? So far we have only specified a success handler at each step in the chain. then also accepts an error handler as an optional second argument. Here's another, longer example of a promise chain, this time with error handling:
var firstFn = function(param) {
// do something with param
if (param == 'bad value') {
return $q.reject('invalid value');
} else {
return 'firstResult';
}
};
var secondFn = function(param) {
// do something with param
if (param == 'bad value') {
return $q.reject('invalid value');
} else {
return 'secondResult';
}
};
var thirdFn = function(param) {
// do something with param
return 'thirdResult';
};
var errorFn = function(message) {
// handle error
};
var deferred = $q.defer();
var promise = deferred.promise.then(firstFn).then(secondFn).then(thirdFn, errorFn);
As you can see in this example, each handler in the chain has the opportunity to divert traffic to the next error handler instead of the next success handler. In most cases you can have a single error handler at the end of the chain, but you can also have intermediate error handlers that attempt recovery.
To quickly return to your examples (and your questions), I'll just say that they represent two different ways to adapt Facebook's callback-oriented API to Angular's way of observing model changes. The first example wraps the API call in a promise, which can be added to a scope and is understood by Angular's templating system. The second takes the more brute-force approach of setting the callback result directly on the scope, and then calling $scope.$digest() to make Angular aware of the change from an external source.
The two examples are not directly comparable, because the first is missing the login step. However, it's generally desirable to encapsulate interactions with external APIs like this in separate services, and deliver the results to controllers as promises. That way you can keep your controllers separate from external concerns, and test them more easily with mock services.
I expected a complex answer that will cover both: why they are used in
general and how to use it in Angular
This is the plunk for angular promises MVP (minimum viable promise): http://plnkr.co/edit/QBAB0usWXc96TnxqKhuA?p=preview
Source:
(for those too lazy to click on the links)
index.html
<head>
<script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.1.5/angular.js"></script>
<script src="app.js"></script>
</head>
<body ng-app="myModule" ng-controller="HelloCtrl">
<h1>Messages</h1>
<ul>
<li ng-repeat="message in messages">{{ message }}</li>
</ul>
</body>
</html>
app.js
angular.module('myModule', [])
.factory('HelloWorld', function($q, $timeout) {
var getMessages = function() {
var deferred = $q.defer();
$timeout(function() {
deferred.resolve(['Hello', 'world']);
}, 2000);
return deferred.promise;
};
return {
getMessages: getMessages
};
})
.controller('HelloCtrl', function($scope, HelloWorld) {
$scope.messages = HelloWorld.getMessages();
});
(I know it doesn't solve your specific Facebook example but I find following snippets useful)
Via: http://markdalgleish.com/2013/06/using-promises-in-angularjs-views/
Update 28th Feb 2014: As of 1.2.0, promises are no longer resolved by templates.
http://www.benlesh.com/2013/02/angularjs-creating-service-with-http.html
(plunker example uses 1.1.5.)
A deferred represents the result of an asynchronic operation. It exposes an interface that can be used for signaling the state and the result of the operation it represents. It also provides a way to get the associated promise instance.
A promise provides an interface for interacting with it’s related deferred, and so, allows for interested parties to get access to the state and the result of the deferred operation.
When creating a deferred, it’s state is pending and it doesn’t have any result. When we resolve() or reject() the deferred, it changes it’s state to resolved or rejected. Still, we can get the associated promise immediately after creating a deferred and even assign interactions with it’s future result. Those interactions will occur only after the deferred rejected or resolved.
use promise within a controller and make sure the data is available or not
var app = angular.module("app",[]);
app.controller("test",function($scope,$q){
var deferred = $q.defer();
deferred.resolve("Hi");
deferred.promise.then(function(data){
console.log(data);
})
});
angular.bootstrap(document,["app"]);
<!DOCTYPE html>
<html>
<head>
<script data-require="angular.js#*" data-semver="1.3.0-beta.5" src="https://code.angularjs.org/1.3.0-beta.5/angular.js"></script>
</head>
<body>
<h1>Hello Angular</h1>
<div ng-controller="test">
</div>
</body>
</html>

Resources