AngularJS promise - angularjs

AngularJS docs say:
$q promises are recognized by the templating engine in angular, which means that in templates you can treat promises attached to a scope as if they were the resulting values.
So could someone please explain the reason this fiddle not working? It's not possible to change text field value. But assigning promises that $http service returns to a scope field works like a charm.
Controller:
function MyController($scope, $q, $timeout) {
this.getItem = function () {
var deferred = $q.defer();
deferred.resolve({
title: 'Some title'
});
return deferred.promise;
};
$scope.item = this.getItem();
}
Html:
<input type="text" ng-model="item.title">

You need to use the then() function on the promise object:
this.getItem().then(function(result) {
$scope.item = result;
});
In your case I don't think you need a promise. Angular's $watch system will take care of things. Just return an object in your function, not a primitive type:
this.getItem = function () {
var item = {};
// do some async stuff
$http.get(...).success(function(result) {
item.title = result;
});
return item;
};
$scope.item = this.getItem();

I believe the reason your first fiddle does not work is because you are essentially binding scope property item to a promise. When you attempt to alter the value by typing into the text field, angular notices the activity, and then reassigns/resets the value of item to the result of the promise (which hasn't changed).
The solution provided by #asgoth sets/assigns the value of item once, when the promise is resolved. There is no binding going on here (i.e, item is not bound to the promise), so altering the value via the textbox does alter the value.

Its like #Mark said, here you can find a Working Example of your snippet.
Basically you were returning an object and not binding the model itself.
$timeout(function(){
$scope.item = {
title: 'Some title'
}; // Apply the binding
deferred.resolve(); // Resolve promise
},2000); // wait 2 secs

Related

Data in callback when using 'Controller as' pattern in Angular

I have the simplest angular controller:
tc.controller('PurchaseCtrl', function () {
var purchase = this;
purchase.heading = 'Premium Features';
this.onSuccess = function (response) {
console.log('Success', response);
lib.alert('success', 'Success: ', 'Loaded list of available products...');
purchase.productList = response;
};
this.onFail = function (response) {
console.log('Failure', response);
};
console.log('google.payments.inapp.getSkuDetails');
lib.alert('info', 'Working: ', 'Retreiving list of available products...');
google.payments.inapp.getSkuDetails(
{
'parameters': {'env': 'prod'},
'success': purchase.onSuccess,
'failure': purchase.onFail
});
});
And the view:
<div class="col-md-6 main" ng-controller="PurchaseCtrl as purchase">
{{purchase}}
</div>
This prints out:
{"heading":"Premium Features"}
I thought that when the callback returned, the view would be update with any new data. Am I missing something? The callback returns and I see the dtaa in the console.
Using the $scope pattern I think that I would use $scope.$apply to async method, but I'm not sure how to do that here.
Using controllerAs does not change the way digest cycle works or anything. It is just a sugar that adds a property (with the name same as alias name when used) to the current scope with its value pointing to the controller instance reference. So you would need to manually invoke the digest cycle (using scope.$apply[Asyc]() or even with a dummy $timeout(angular.noop,0) or $q.when() etc) in this case as well. But you can avoid injecting scope by abstracting it out into an angular service and returning a promise from there, i.e
myService.$inject = ['$q'];
function myService($q){
//pass data and use it where needed
this.getSkuDetails = function(data){
//create deferred object
var defer = $q.defer();
//You can even place this the global variable `google` in a
//constant or something an inject it for more clean code and testability.
google.payments.inapp.getSkuDetails({
'parameters': {'env': 'prod'},
'success': function success(response){
defer.resolve(response);// resolve with value
},
'failure': function error(response){
defer.reject(response); //reject with value
}
});
//return promise
return defer.promise;
}
}
//Register service as service
Now inject myService in your controller and consume it as:
myService.getSkuDetails(data).then(function(response){
purchase.productList = response;
}).catch(function(error){
//handle Error
});

Assign $scope variables inside a promise then method for angular datatables directive?

I am not sure what the issue is, but it seems the datatables directive is being initialized before my $scope is defined. What's more, it seems the variables getting set twice.
I have a userService that retrieves the column definitions and data from my server. The getUserList() method returns a promise. I use the then() method to set the $scope variables that will be used by the datatables directive. It seems the directive is requesting the variables before the request completes. Also, it seems the variables are getting set twice, because in Chrome's dev console I see the "test" log twice.
If I use static data (not coming from the server) and place the $scope variables outside the getUserList() promise it works fine.
$scope.indexList = function () {
userService.getUserList().then(function (data) {
$scope.dtOptions = DTOptionsBuilder.fromFnPromise(function () {
console.log("test");
var deferred = $q.defer();
deferred.resolve(data.Data);
return deferred.promise;
});
$scope.dtColumns = [];
angular.forEach(data.DataTablesColumns, function (i, v) {
$scope.dtColumns.push(DTColumnBuilder.newColumn(i.Id)
.withTitle(i.DisplayName)
.renderWith(actionsHtml));
});
});
}
This is how I am setting the datatables directive:
<div ng-init="indexList()">
<table datatable="" dt-options="dtOptions" dt-columns="dtColumns"class="row-border hover"></table>
</div>
The directive code is executed as soon as the page loads. And since your $scope variables are defined in the promise, they are not available at the time of page load.
The directive does not wait for the request to complete because requests are async in nature. If you want the directive variable to be updated when the request completes you have to set a $watch(or $watchGroup if you want to watch multiple variables) on the $scope.variables in your link function as follows:
link: function(scope, element, attrs) {
scope.$watch('dtOptions', function(newval, oldval) {
//your directive code involving scope variables here.
})
}
It seems I have to make 2 calls to my server to do what I am after. This works, but I feel there should be a way to make a single call to the server and get back then entire result. After some reading the options and column builders can take a promise.
$scope.indexList = function () {
$scope.dtOptions = DTOptionsBuilder.fromFnPromise(function () {
return userService.getUserList(ngRequestGlobabls.context.organization).then(function(data) {
return data.Data;
});
});
$scope.dtColumns = userService.getUserList(ngRequestGlobabls.context.organization).then(function (data) {
columns = [];
angular.forEach(data.DataTablesColumns, function(i, v) {
columns.push(DTColumnBuilder.newColumn(i.Id).withTitle(i.DisplayName).renderWith(actionsHtml));
});
return columns;
});

AngularJS : Service data binding

I am trying to call a service in angular.js through a controller on load and return a promise. I then expect the promise to be fulfilled and for the DOM to be updated. This is not what happens. To be clear, I am not getting an error. The code is as follows.
app.controller('TutorialController', function ($scope, tutorialService) {
init();
function init() {
$scope.tutorials = tutorialService.getTutorials();
}
});
<div data-ng-repeat="tutorial in tutorials | orderBy:'title'">
<div>{{tutorial.tutorialId}}+' - '+{{tutorial.title + ' - ' + tutorial.description}}</div>
</div>
var url = "http://localhost:8080/tutorial-service/tutorials";
app.service('tutorialService', function ($http, $q) {
this.getTutorials = function () {
var list;
var deffered = $q.defer();
$http({
url:url,
method:'GET'
})
.then(function(data){
list = data.data;
deffered.resolve(list);
console.log(list[0]);
console.log(list[1]);
console.log(list[2]);
});
return deffered.promise;
};
});
Inside of the ".then()" function in the service, I log the results and I am getting what I expected there, it just never updates the DOM. Any and all help would be appreciated.
getTutorials returns promise by itself. So you have to do then() again.
tutorialService.getTutorials().then(function(data){
$scope.tutorials = data;
});
Before that, $http returns a promise with success() and error().
Although you can also use then as well
Since the returned value of calling the $http function is a promise,
you can also use the then method to register callbacks, and these
callbacks will receive a single argument – an object representing the
response.
So you are correct with that.
What is your data coming from the http call look like? Your code works - I created a version of it here http://jsfiddle.net/Cq5sm/ using $timeout.
So if your list looks like:
[{ tutorialId: '1',
title : 'the title',
description: 'the description'
}]
it should work
In newer Angular versions (I think v 1.2 RC3+) you have to configure angular to get the unwrap feature working (DEMO):
var app = angular.module('myapp', []).config(function ($parseProvider) {
$parseProvider.unwrapPromises(true);
});
This allows you to directly assign the promise to the ng-repeat collection.
$scope.tutorials = tutorialService.getTutorials();
Beside that I personally prefer to do the wrapping manually:
tutorialService.getTutorials().then(function(tutorials){
$scope.tutorials = tutorials;
});
I don't know the exact reason why they removed that feature from the default config but it looks like the angular developers prefer the second option too.

$q.all and creation of a new object not working in the view

I'm having difficulty with $q.all not resolving in AngularJS. I may not be describing things correctly, but this is at least the scenario.
The Service
app.factory('myService', function($http){
return {
get: function () {
return $http.get('http://localhost:8001/');
}
};
});
In the Controller
angular.forEach(array, function(item){
promises.push({foo:myService.get(), bar: item});
});
$q.all(promises).then(function(results){
$log.info(results);
$scope.results = results;
});
In the view, anything taking values from bar displays correctly. Anything, however, taking its values from foo is blank. In the console bar is an inspectable object as you'd expect. But foo is still a promise. It has all the methods a promise would, but no data associated.
So, somehow, $q.all isn't resolved by the time the results are applied to the $scope. And why, I'm not sure.
I'll note that I've based what I'm doing above somewhat on what's here: angular.forEach and objects
$q.all is expecting an array of promises. try something like this:
angular.forEach(array, function(item){
var foo = myService.get();
var bar = myOtherService.get();
promises.push(foo);
promises.push(bar);
});
$q.all(promises).then(function(results){
$log.info(foo);
$log.info(bar);
});
UPDATE
if you need you could nest multiple $q.all
angular.forEach(array, function(item){
var deferred = $q.defer();
$q.all([myService.get(), myOtherService.get()]).then(function(results){
deferred.resolve({foo:results[0], bar: results[1]});
});
promises.push(deferred.promise);
});
$q.all(promises).then(function(results){
$log.info(results);
});
Can you make a plnkr? Also your array of "promises" aren't promises but a custom object where one property is the promise and the other is something else. If you want to make a promise hash it should look like this:
var promises = {};
angular.forEach(array, function(item){
promises[item.foo] = item.promise;
});
$q.all(promises).then(...

Angular Editable Field Reverting to Original Name After Change

I am trying to remotely get a user's name and then have it editable so that it can be saved again. I am getting the name correctly, but when I try to change it, it reverts back to the old name.
Am I doing something wrong with the defer and resolve?
var deferred = $q.defer();
$http({
url : '/getuser',
method : 'GET',
}).success(function(data) {
deferred.resolve(data);
});
return deferred.promise;
http://jsfiddle.net/NfPcH/181/
You're assigning the promise directly to your model variable. So even though in many ways your variable acts like it got the results of the promise it actually instead contains a binding to the promise.
So your variable keeps getting set to resolved value of the promise during $digest cycles - overwriting your edits.
So instead of binding your variable to the promise like this:
$scope.myUser = getUser();
Use the promise's then method and assign the results of the promise. This way myUser is initialized to the results of your promise just once instead of being perpetually bound to the promise itself.
getUser().then(function(result) {
$scope.myUser = result;
});
For this example, you don't really need to have an explicit promise. $http already returns a promise so you really don't need to define a new one.
I've re-arranged the code a bit. In order to initialize the value, I'm using ng-init to call the getUser() method. This then calls $http and binds to the $scope.myUser. BTW, I noticed that in only ever enters the error callback.
Because promises are async, you must bind to the $scope inside the success or error callback function of $http.
The reason it was reverting to the 'old' value was because you were binding directly to the function getUser(). This meant that angular was setting up a $watch on the return value of this function.
Here is a working fiddle.
HTML:
<h4>remote</h4>
<div ng-app="app" ng-controller="Ctrl" ng-init="init()">
<a href="#" editable-text="myUser.name" >
{{ myUser.name || 'not set!' }}
</a>
</div>
JS:
app.controller('Ctrl', function($scope, $http, $q) {
$scope.myUser = null;
$scope.init = function() {
getUser();
};
function getUser() {
return $http({
url : 'http://espn.com/images/test-data/public-profile.json',
method : 'GET',
})
.error(function(data) {
$scope.myUser = {"name":"bobby","age":"21"};
})
.success(function(data) {
console.log(data);
$scope.myUser = data;
});
};
});

Resources