I have the following controller code:
.controller('Controller1', function ($scope, MyService) {
var promise = MyService.getData();
promise.then(function(success) {
console.log("success");
}, function(error) {
console.log("error");
}, function(update) {
console.log("got an update!");
}) ;
}
And in my services.js:
.factory('MyService', function ($resource, API_END_POINT, localStorageService, $q) {
return {
getData: function() {
var resource = $resource(API_END_POINT + '/data', {
query: { method: 'GET', isArray: true }
});
var deferred = $q.defer();
var response = localStorageService.get("data");
console.log("from local storage: "+JSON.stringify(response));
deferred.notify(response);
resource.query(function (success) {
console.log("success querying RESTful resource")
localStorageService.add("data", success);
deferred.resolve(success);
}, function(error) {
console.log("error occurred");
deferred.reject(response);
});
return deferred.promise;
}
}
})
But for some reason the deferred.notify call never seems to execute and be received within the controller. Have I don't something wrong here? I'm not sure how to get the notify to execute.
I managed to get it working by wrapping notify in $timeout function:
$timeout(function() {
deferred.notify('In progress')
}, 0)
Looks like you cant call notify before you return promise object, that kinda makes sense.
source: http://www.bennadel.com/blog/2800-forcing-q-notify-to-execute-with-a-no-op-in-angularjs.htm
Forcing $q .notify() To Execute
The beauty of the .notify() event is that our data-access layer can use it serve up the "immediately available, yet stale" data while still using the .resolve() event for nothing but live data. This gives the calling context - your controller - great insight and control over which dataset is cached and whether or not it [the controller] even wants to incorporate cached data.
But, we run into a little bit of a race condition. The data-access service, that owns the cached data, needs to call .notify() before it returns the promise to the calling context. This means that your controller binds to the notify event after .notify() has been called. From a philosophical standpoint, this should be fine - Promises (and just about everything that is event-driven) are intended to invoke bindings asynchronously in order to create uniformity of access.
From a practical standpoint, however, it's not quite that simple. While AngularJS follows this philosophy, it also adds a few optimizations to cut down on processing. In our case specifically, AngularJS won't schedule the callback-processing in a deferred object unless it sees that at least one callback is bound (otherwise it thinks the world isn't listening). As such, our controller will never be notified about the cached data.
To get around this, we can have our service layer bind a no-op (no operation) function to the notify event before it calls .notify(). This way, when it does call .notify(), AngularJS will see that at least one callback is registered and it will scheduled a flushing of the pending queue in the next tick (which is implemented via $rootScope.$evalAsync()). This allows our controller to get notified of cached data even if it binds to the notify event after .notify() has been invoked.
To see this in action, I've created a friendService that returns data through two different methods. Both of the methods attempt to return cached data via .notify() and then "live" data via .resolve(). The only difference between the two methods is that one binds a no-op to the notify event before calling .notify()
<!doctype html>
<html ng-app="Demo">
<head>
<meta charset="utf-8" />
<title>
Forcing $q .notify() To Execute With A No-Op In AngularJS
</title>
<link rel="stylesheet" type="text/css" href="./demo.css"></link>
</head>
<body ng-controller="AppController">
<h1>
Forcing $q .notify() To Execute With A No-Op In AngularJS
</h1>
<h2>
Friends
</h2>
<div ng-switch="isLoading">
<!-- Show while friends are being loaded. -->
<p ng-switch-when="true">
<em>Loading...</em>
</p>
<!-- Show once the friends have loaded and are available in the view-model. -->
<ul ng-switch-when="false">
<li ng-repeat="friend in friends track by friend.id">
{{ friend.name }}
</li>
</ul>
</div>
<p>
<a ng-click="load()">Load</a>
|
<a ng-click="loadWithNoop()">Load With No-Op</a>
</p>
<!-- Load scripts. -->
<script type="text/javascript" src="../../vendor/angularjs/angular-1.3.13.min.js"></script>
<script type="text/javascript">
// Create an application module for our demo.
var app = angular.module( "Demo", [] );
// -------------------------------------------------- //
// -------------------------------------------------- //
// I control the root of the application.
app.controller(
"AppController",
function( $scope, friendService ) {
$scope.isLoading = false;
$scope.friends = [];
// Load the friend data (defaults to "get" method vs. "getWithNoop").
loadRemoteData();
// ---
// PUBLIC METHODS.
// ---
// I reload the list of friends using friendService.get().
$scope.load = function() {
loadRemoteData( "get" );
};
// I reload the list of friends using friendService.getWithNoop().
$scope.loadWithNoop = function() {
loadRemoteData( "getWithNoop" );
};
// ---
// PRIVATE METHODS.
// ---
// I load the friends from the friend repository. I am passing-in the
// method name to demonstrate that, from the Controller's point-of-view,
// nothing here is different other than the name of the method. The real
// substantive difference exists in the implementation of the friend-
// Service method and how it interacts with $q / Deferred.
function loadRemoteData( loadingMethod ) {
console.info( "Loading friends with [", loadingMethod, "]" );
// Indicate that we are in the loading phase.
$scope.isLoading = true;
// When we make the request, we expect the service to try to use
// cached-data, which it will make available via the "notify" event
// handler on the promise. As such, we're going to wire up the same
// event handler to both the "resolve" and the "notify" callbacks.
friendService[ loadingMethod || "get" ]
.call( friendService )
.then(
handleResolve, // Resolve.
null,
handleResolve // Notify.
)
;
function handleResolve( friends ) {
// Indicate that the data is no longer being loaded.
$scope.isLoading = false;
$scope.friends = friends;
console.log( "Friends loaded successfully at", ( new Date() ).getTime() );
}
}
}
);
// -------------------------------------------------- //
// -------------------------------------------------- //
// I provide access to the friend repository.
app.factory(
"friendService",
function( $q, $timeout ) {
// Our friend "repository".
var friends = [
{
id: 1,
name: "Tricia"
},
{
id: 2,
name: "Heather"
},
{
id: 3,
name: "Kim"
}
];
// Return the public API.
return({
get: get,
getWithNoop: getWithNoop
});
// ---
// PUBLIC METHODS.
// ---
// I return the list of friends. If the friends are cached locally, the
// cached collection will be exposed via the promise' .notify() event.
function get() {
var deferred = $q.defer();
// Notify the calling context with the cached data.
deferred.notify( angular.copy( friends ) );
$timeout(
function networkLatency() {
deferred.resolve( angular.copy( friends ) );
},
1000,
false // No need to trigger digest - $q will do that already.
);
return( deferred.promise );
}
// I return the list of friends. If the friends are cached locally, the
// cached collection will be exposed via the promise' .notify() event.
function getWithNoop() {
var deferred = $q.defer();
// -- BEGIN: Hack. ----------------------------------------------- //
// CAUTION: This is a work-around for an optimization in the way
// AngularJS implemented $q. When we go to invoke .notify(),
// AngularJS will ignore the event if there are no pending callbacks
// for the event. Since our calling context can't bind to .notify()
// until after we invoke .notify() here (and return the promise),
// AngularJS will ignore it. However, if we bind a No-Op (no
// operation) function to the .notify() event, AngularJS will
// schedule a flushing of the deferred queue in the "next tick,"
// which will give the calling context time to bind to .notify().
deferred.promise.then( null, null, angular.noop );
// -- END: Hack. ------------------------------------------------- //
// Notify the calling context with the cached data.
deferred.notify( angular.copy( friends ) );
$timeout(
function networkLatency() {
deferred.resolve( angular.copy( friends ) );
},
1000,
false // No need to trigger digest - $q will do that already.
);
return( deferred.promise );
}
}
);
</script>
</body>
</html>
As you can see, the controller binds the same handler to the "resolve" and "notify" event of the promise. In this way, it can handle the cached data and the live data uniformly. The only difference is in which service-layer method it invokes - get() vs. getWithNoop(). And, if we invoke .get() a few times and then .getWithNoop() a few times, we can see the difference in the console.
I tried to reproduce your problem here. It seems, that you cannot call notifyon the promise directly, but have to wrap into an $applycall.
See also the documentation for $q here.
To quote the exact lines from the example:
since this fn executes async in a future turn of the event loop, we need to wrap our code into an $apply call so that the model changes are properly observed.
You could try this your self and change your code a little bit:
deferred.notify(response); // should not work
resource.query(function (success) {
deferred.notify('Returning from resource'); // should work
console.log("success querying RESTful resource")
localStorageService.add("data", success);
deferred.resolve(success);
}, function(error) {
deferred.notify('caught error!'); //should also work
console.log("error occurred");
deferred.reject(response);
});
Related
I have an AngularJs app and use RXJS. I have a service that waits for a user to allow or deny access to an OAuth provider and has this signature:
authorise( requestDetails : IRequestDetails ) : Rx.Observable<string>;
When the access is allowed and I get my access token back I pass this back to the controller like this:
request.observable.onNext( request.access_token );
request.observable.onCompleted();
and the controller passed this back to the view by updating the backer variable for the message:
private _message : string = "Awaiting authorisation";
get message() : string {
return this._message;
}
authenticate() : void
{
this._oAuthService.authorise( Example.googleAuthDetails ).subscribe(
( result : string ) => this.handleAuthorisation( result ),
( fault : string ) => this.handleError( fault )
);
}
private handleAuthorisation( result : string ) : void {
this._message = result;
}
This all works fine. I have discovered that the reason it works in this case is because the onNext call is made as part of a result from a $http call. This call is wrapped in a call to $apply that triggers a digest cycle.
However when the user denies access I call onError on the observable:
request.observable.onError( "access token denied" );
request.observable.onCompleted();
and in the controller I update the string with the error:
private handleError( fault : string ) : void {
this._message = fault;
}
but in this case the view does not update and the displayed string remains as "Awaiting Authorisation".
In this case as the authorisation was denied no $http call is made and the controller is triggered in a function that is not wrapped in an $apply call.
I have included the rx.angular.js library in my html page hoping that would automatically fix it but it hasn't. Reading the docs it seems that I might have to watch something specifically. I thought that I could just get it work with RXJS and always update the scope when something updates.
I don't want to inject scopes into my controller to force a refresh if I don't have to. I don't like having a dependency on that.
The whole project is available to view in this branch:
https://github.com/Roaders/Typescript-OAuth-SPA/blob/rxjs_issues/index.html
Just wrap everything into a $timeout call, it will ensure that a digest is called.
RX.angular seems nice, but I think it is a little outdated with the new norm to not use $scope everywhere.
var myAppModule = angular.module('myApp', []).controller('ctrl', SettingsController1);
function SettingsController1($timeout) {
this.message = "waiting";
this.observable = new Rx.ReplaySubject(2 /* buffer size */ );
var _this = this;
this.observable.subscribe(
function(x) {
$timeout(function() {
_this.message = x;
}, 0);
},
function(err) {
$timeout(function() {
_this.message = err;
}, 0);
},
function() {
this.message = 'completed';
});
this.success = function() {
this.observable.onNext('success');
}
this.error = function() {
this.observable.onError('failed');
}
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/angular.js/1.4.8/angular.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/rxjs/4.0.7/rx.all.min.js"></script>
<div ng-app="myApp" ng-controller="ctrl as ctrl">
<div>
{{ ctrl.message }}
</div>
<button ng-click="ctrl.success()">
Success
</button>
<button ng-click="ctrl.error()">
Error
</button>
</div>
This solution is implemented in this branch: https://github.com/Roaders/Typescript-OAuth-SPA/tree/rx_solved_with_timeout
The reason that it didn't work for the error case is because there was no http call to trigger a digest cycle so we need to trigger a digest cycle in this case.
To do that we need to inject the $rootScope into the service and when we call onError we need to wrap it in a $rootScope.$apply call:
this._scope.$apply( () => {
request.observable.onError( "access token denied" );
request.observable.onCompleted();
} );
This solution is here:
https://github.com/Roaders/Typescript-OAuth-SPA/tree/rx_solved_with_scope
I know that I said I didn't want a scope injected but now I have a better understanding of why I need to I think I am OK with this. I am also happier about injecting the root scope rather than a controller scope that is not used for anything else.
I am having problem with setting up a service that will perform asynchronous http request.
Background
I have a backend endpoint /cars.json When you hit this endpoint for the first time the background job which will fetch all the cars information gets started. Server then returns response with status 200:
{"status":"started", "percentage_completion":0, "data":null, "error":null}
Consecutive requests to this endpoint will keep on returning status 200 with updated percentage_completion
{"status":"started", "percentage_completion":45, "data":null, "error":null}
{"status":"started", "percentage_completion":90, "data":null, "error":null}
Finally the request to the endpoint will return:
{"status":"finished", "percentage_completion":100, "data": {...}, "error":null}
and happy days I have a data and I can add it to the $scope.
But I am having hard time to get my head around the promises and angular way of doing things...
I have spiked something like this:
var dashboard = angular.module('dashboard', [
'templates'
]);
dashboard.controller('dashboardController', ['carsService', '$scope',
function(carsService, $scope){
$scope.test = "Loading...";
carsService.get()
.then(function(response){
$scope.test = response.data;
});
}]
);
dashboard.factory('carsService', ['$http', function($http){
var get = function(){
return $http.get('/cars.json');
};
return {
get: get
};
}]);
It sends a single request to the server and updates the test on the scope with the first response stating the job has been started. When I refresh the page after few seconds I get the test updated with a correct data and the finish status.
What would be a best way, angular way:) with promises and stuff to have the service and controller do it automatically.
First thing that comes to my head is to use a $interval or something similar and periodically resend the request from .then(success) but maybe there is a better way.
Angular documentation says something about progress/notify promise but at the moment I have no idea how to wire it in.
Obviously I don't fully understand the promises at the moment, I just got introduced to them - can you share some tips/resources on how to deal with asynchronous requests using angular?
I am not positive that I understand your question fully, but if you are wanting to get the progress updates as they come back, Angular has a progress callback built into their promise service (as you mention). The first callback inside your .then() is your success callback, the second is your error callback, and the third would be your progress callback.
So when chaining your callbacks onto the promise, you can use the following syntax to update a progress indicator:
carsService.get()
.then(function(response){
// success
$scope.test = response.data;
$scope.$apply(); // or wrapped in $timeout
}, function(error){
// error
}, function(percentComplete){
// progress
$scope.progress = percentComplete;
$scope.$apply(); // or wrapped in $timeout
});
As to the best syntax to use when writing and chaining your promises, John Papa has a fantastic (and very in-depth) style guide that will help you better organize your Angular code. You can find this here.
Here's a sample jsfiddle demonstrating this notify/progress callback in action, as well. It is using a timeout to simulate the async call, but displays the general idea well.
Also remember that when you make these async calls, you may need to run $scope.$apply() or wrap the code in the callback where you update your scope data inside of a $timeout(function(){ //update data }); in order to run the Angular digest cycle again (async calls update your data outside of Angular's built-in watchers). This will then cause your view to update, as well, without having to reload a page. There are quite a few blog posts and SO questions regarding this that you can use if you've never run into this behavior before. Here's one that covers it pretty well.
I guess you can use nested promise to achieve progress notify:
function get(){
var defer = $q.defer();
(function fetchData(){
$http.get('/cars.json').then(function(result){
if(result.status === 'started'){
fetchData();
defer.notify(result);
}else if(result.status === 'finished'){
defer.resolve(result);
}
}, function(err){
defer.reject(err);
})
})()
return defer.promise;
}
when you call get(), it first call fetchData to request data from server. When fetchData resolved, check result.status, if status is not finished, call fetchData again and notify outter defer, otherwise, resolve outter defer with final result
//in controller
carService.get().then(
function(finalData){ $scope.test = finalData },
function(err){ ... },
function(notifyData){ $scope.test = notifyData }
}
All the credit goes to MarkoCen for his suggestion on using chained promises. This is my final solution:
app.js.coffee
angular
.module 'dashboard', ['templates']
car_service.coffee
CarService = ($q, $http)->
get = (url,defer)->
$http.get(url).then(
(result)->
data = result.data
if data.status == "started"
get(url, defer)
defer.notify(data.percentage_completion)
else
defer.resolve(data.data.cars)
)
defer.promise
return{
getAll: (url)->
defer = $q.defer()
get(url,defer)
}
angular
.module 'dashboard'
.factory 'CarService', [
'$q'
'$http'
CarService
]
cars_controller.coffee
DashboardController = (CarService) ->
vm = #
vm.campaigns = []
vm.statusBar = {}
CarService.getAll('/cars.json').then(
(data)->
vm.campaigns = data
(reason)->
console.log reason
(update)->
vm.statusBar.percentage = update
)
return
angular
.module 'dashboard'
.controller 'DashboardController', [
'CarService'
DashboardController
]
Here there is a reference about $http service in AngularJS.
https://docs.angularjs.org/api/ng/service/$http
This is the explanation about the $http service and how to use then()
function.
// Simple GET request example:
$http({
method: 'GET',
url: '/someUrl'
}).then(function successCallback(response) {
// this callback will be called asynchronously
// when the response is available
}, function errorCallback(response) {
// called asynchronously if an error occurs
// or server returns response with an error status.
});
The best way is using HTML5 Server-Sent Events or WebSockets technology where you can get data in realtime.
In this demo I'm using anonymous function inside the then() function.
I've made two very simple demos by using PHP, Server Sent Events, JSON and AngularJS Factory service.
AngularJS Factory Service and PHP JSON Response: cars.php
<?php
header("Access-Control-Allow-origin: *"); // To allow cross-origin HTTP request.
header("Content-Type: application/json"); // JSON response.
header("Cache-Control: no-cache"); // No cache.
/*
Only to display random numeric values.
In a real scenario the data can be obtained from database.
*/
$min = 0;
$max = 100;
$count = rand($min, $max); // Get a random numeric value.
$status = "started";
if($count == $max)
{
$status = "finished"; // If the $count == 100 then finished.
}
// Builing an array with current data.
$array = array("status" => $status, "percentage_completion" => $count, "data" => null, "error" => null);
echo json_encode($array); // Encode the array to the json representation.
?>
(function() {
var dashboard = angular.module("dashboard", []);
dashboard.controller("dashboardController", ["carsService", "$scope",
function(carsService, $scope) {
$scope.statusJSON = "Loading...";
$scope.testJSON = {};
$scope.initJSON = function() {
(function loop() { // Looping function to continous request.
carsService.get().then(function(response) { // Angular promise by using then() function.
$scope.testJSON = response.data;
$scope.statusJSON = $scope.testJSON.status;
console.log($scope.testJSON);
}, function(response) {
console.log("Error: " + response.status);
});
setTimeout(loop, 1000); // Call the $http service every 1 second.
})();
};
}
]);
dashboard.factory("carsService", ["$http",
function($http) {
return { // This factory service returns an object with the get function.
get: function() {
return $http.get("http://dfjb.webcindario.com/cars.php", {
responseType: "json"
}); // Returns a $http service.
}
};
}
]);
})();
.json {
border: solid 1px #444444;
margin: 5px;
padding: 5px;
}
<html data-ng-app="dashboard">
<head>
<title>Demo AngularJS</title>
<script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.2.23/angular.min.js"></script>
</head>
<body data-ng-controller="dashboardController">
<div class="json" data-ng-init="initJSON()">
<h3>Demo With Angular $http Service</h3>
<div data-ng-bind="statusJSON"></div>
percentage_completion: {{testJSON.percentage_completion}}
<br />
<progress min="0" max="100" value="{{testJSON.percentage_completion}}" />
</div>
</body>
</html>
HTML5 Server-Sent Events: data.php
<?php
header("Access-Control-Allow-origin: *"); // To allow cross-origin HTTP request.
header("Content-Type: text/event-stream"); // To send event streams.
header("Cache-Control: no-cache"); // No cache.
/*
Only to display random numeric values.
In a real scenario the data can be obtained from database.
*/
$min = 0;
$max = 100;
$count = rand($min, $max); // Get a random numeric value.
$status = "started";
if($count == $max)
{
$status = "finished"; // If the $count == 100 then finished.
}
// Builing an array with current data.
$array = array("status" => $status, "percentage_completion" => $count, "data" => null, "error" => null);
echo "data: ". json_encode($array) ."\n\n"; // Encode the array to the json representation. The event-stream always start with "data: ".
flush(); // Flush the output data back to the web page.
?>
(function() {
var dashboard = angular.module("dashboard", []);
dashboard.controller("dashboardController", ["carsService", "$scope",
function(carsService, $scope) {
$scope.statusSSE = "Loading...";
$scope.testSSE = {};
$scope.initSSE = function() {
// Check if EventSource is supported by the browser.
if (typeof(EventSource) !== "undefined") {
carsService.getDataSSE().onmessage = function() {
// The factory service has a function that returns an EventSource object, so this can be accessed in the controller.
$scope.testSSE = JSON.parse(event.data); // Parse the string in an object.
$scope.statusSSE = $scope.testSSE.status;
$scope.$apply(); // Update the $scope variable so can be used in the view.
console.log($scope.testSSE);
};
} else {
alert("SSE not supported by browser.");
}
};
}
]);
dashboard.factory("carsService", [
function() {
return {
getDataSSE: function() {
// HTML5 Server-Sent Events Implementation.
return new EventSource("http://dfjb.webcindario.com/data.php"); // Returns the EventSource object.
}
};
}
]);
})();
.sse {
border: solid 1px #FF44AA;
margin: 5px;
padding: 5px;
}
<html data-ng-app="dashboard">
<head>
<title>Demo AngularJS</title>
<script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.2.23/angular.min.js"></script>
</head>
<body data-ng-controller="dashboardController">
<div class="sse" data-ng-init="initSSE()">
<h3>Demo With HTML5 Server-Sent Events in PHP</h3>
<div data-ng-bind="statusSSE"></div>
percentage_completion: {{testSSE.percentage_completion}}
<br />
<progress min="0" max="100" value="{{testSSE.percentage_completion}}" />
</div>
</body>
</html>
Finally, in this demo, I'm showing you how to use realtime notifications from server by using AngularJS Factory service in PHP and HTML5 Server-Sent Events Technology with json response.
If you check the console you get a json string in the EventStream tab automatically.
Forgive my ignorance, I'm new to Angular and JavaScript.
I've read that promises make it more elegant to call asynchronous services, but I've not been able to find any documentation on how to make my service asynchronous.
If you are simply wrapping a call to $http then there is no additional work needed on your part. All the $http calls return a promise, so you can just pass that along and your API will be async, and promise based.
function MyService($http){
this.$http = $http;
}
MyService.prototype = {
getContacts: function(){
//$http returns a promise already,
// so just pass it along
return $http.get('/api/contacts');
}
};
angular.service('myService', MyService);
//Later on in some controller
function MyController(myService){
var _this = this;
//getContacts() returns the promise
// from $http
myService.getContacts()
.success(function(contacts){
_this.contacts = contacts;
});
}
However...
If you want to create a normal API that executes code asynchronously, then you can wrap that up in a promise just like the example in the docs.
JavaScript is basically* single threaded, so that means that it uses an execution queue for asynchronous code. Execution will go from top to bottom, and whenever it stops, the queue will be emptied out in order.
When you call something like setTimeout or setInterval it is simply placing that code on the queue to be executed whenever the current thread gets around to it, but it isn't happening in the background.
You can test this for yourself, by opening up a browser console and typing this code into it:
setTimeout(function(){ console.log("I'm first!"); }, 0);
console.log("Nope, I am!");
Even though I have set the timeout to be 0 milliseconds, that doesn't mean it will execute immediately. It means it will be placed on the queue to be run immediately once all the other code finishes.
Finally
Try not to think of promises as strictly for managing asynchronous calls. They are a pattern for executing code once some precondition has been met. It just so happens that the most popular precondition is some asynchronous I/O via AJAX.
But the pattern is a great way of managing any operation that must wait for some number of preconditions.
Just to really drive this point home, check out this little snippet that uses a promise to determine when the user has clicked the button more than 5 times in a row.
(function() {
var app = angular.module('promise-example', []);
function PromiseBasedCounter($q, $timeout) {
this.$q = $q;
this.$timeout = $timeout;
this.counter = 0;
this.counterDef = $q.defer();
}
PromiseBasedCounter.$inject = ['$q', '$timeout'];
PromiseBasedCounter.prototype = {
increment: function() {
var _this = this;
//$timeout returns a promise, so we can
// just pass that along. Whatever is returned
// from the inner function will be the value of the
// resolved promise
return _this.$timeout(function() {
_this.counter += 1;
//Here we resolve the promise we created in the
// constructor only if the count is above 5
if (_this.counter > 5) {
_this.counterDef.resolve("Counter is above 5!");
}
return _this.counter;
});
},
countReached: function() {
//All defered objects have a 'promise' property
// that is an immutable version of the defered
// returning this means we can attach callbacks
// using the promise syntax to be executed (or not)
// at some point in the future.
return this.counterDef.promise;
}
};
app.service('promiseBasedCounter', PromiseBasedCounter);
function ClickCtrl(promiseBasedCounter) {
var _this = this;
_this.promiseBasedCounter = promiseBasedCounter;
_this.count = 0;
//Here we set up our callback. Notice this
// really has nothing to do with asyncronous I/O
// but we don't know when, or if, this code will be
// run in the future. However, it does provide an elegant
// way to handle some precondition without having to manually
// litter your code with the checks
_this.promiseBasedCounter.countReached()
.then(function(msg) {
_this.msg = msg;
});
}
ClickCtrl.$inject = ['promiseBasedCounter'];
ClickCtrl.prototype = {
incrementCounter: function() {
var _this = this;
//Whenever we increment, our service executes
// that code using $timeout and returns the promise.
// The promise is resolved with the current value of the counter.
_this.promiseBasedCounter.increment()
.then(function(count) {
_this.count = count;
});
}
};
app.controller('clickCtrl', ClickCtrl);
}());
<link href="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.4/css/bootstrap.min.css" rel="stylesheet" />
<script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.4.0/angular.min.js"></script>
<div ng-app="promise-example" ng-controller="clickCtrl as ctrl">
<div class="container">
<h1>The current count is: <small>{{ctrl.count}}</small></h1>
<button type="button" class="btn btn-lg btn-primary" ng-click="ctrl.incrementCounter()">Click Me!</button>
</div>
<div class="container">
<h1>{{ctrl.msg}}</h1>
</div>
</div>
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>
I am working on a multiple file upload module and am stuck when it comes to communicating from my service to my controller without using $rootScope.
A directive watches the file input and hands the files onchange to a service, where they get uploaded and the upload progress monitored. Based on the response (from success, error and progress changes), the parent controller should show thumbs and progress.
I do not want to use $emit and $on on my $rootScope, since I need the pattern quiet often and only that one parent controller needs to know about the uploads.
I created a simplified(!) Plunkr with some additional information to better understand the problem.
Is there another way for my controller to react to changes (happening inside the service factory)?
Or maybe a completely different way of achieving such?
A proper way to handle async operations with Angular is using promises.
You could return a promise from the service call and resolve it to the src of the thumb. You could then not use a directive at all.
Your controller would use the service this way:
function ParentController($scope, imageService) {
$scope.change = function() {
$scope.src = imageService.change();
$scope.then(function(result) {
// the loading ended, but there is no need to set the src
// as it will already bind to src when ends.
}, function(err) {
// something went wrong
});
};
}
Your service:
app.factory('imageService', function($q, $rootScope) {
function doTheUploading(defer) {
if (everythingOk) {
defer.resolve(thumbSrcString);
} else {
defer.reject({something: 'an error ocurred'});
}
}
return {
change: function() {
// creates the promise
var defer = $q.defer();
doSomethingAsync(defer);
return defer.promise;
}
};
});
At least, your HTML should look like:
<div ng-controller="ParentController">
<img src="{{ src }}"><br><br>
<span class="btn" ng-click="change()">Change!</span>
</div>
Regarding the progress, you will need to go with callbacks or returning an object that would bring the promise and a progress indicator (i.e: return {promise: defer.promise, percent: 0}). The progress indicator should be then updated from inside the service.
You could also chain your promise if you need any transformation in the URL returned from the server before sets it to the src property, i.e.:
$scope.src = imageService.change().then(function(result) {
return 'http://dummyimage.com/' + result;
});
Updated your Plunker.