How can I update my view model from chained promises? - javascript

I'm quite new to promises. I have a hard time trying to update an object used in my view from 2 chained promises :
function Test($resource, FC, UserDetailsService) {
'ngInject';
var self = this;
self.data = {
};
function getTestData() {
firstPromise.then(function(response) {
//I want self.data to be displayed in my view
angular.extend(self.data, response);
//Now my view should display my object
resource().get(user)
.$promise.then(function(responsePts){
//And THEN update/refresh my view here
angular.extend(self.data, responsePts);
});
});
};
self.getTestData = getTestData;
};
EDIT : firstPromise is exposed in another service, and used by other services :
$resource(apiUrl).get(user).$promise.then(function(bookData){
angular.extend(self.bookings, bookData);
});
In my controller :
function TestController(Test) {
'ngInject';
var $ctrl = this;
$ctrl.testData = {};
Test.getTestData();
$ctrl.testData = Test.data;
};
Instead self.data will not be displayed until the resolution of the second promise. How can I make my object available for my controller directly when the first promise is resolved ?

It the firstPromise is a $q Service promise, the view should be updating. Since the view is not updating, use $q.when() to convert the unknown promise to a $q Service promise:
function getTestData() {
//firstPromise.then(function(response) {
$q.when(firstPromise).then(function(response) {
//I want self.data to be displayed in my view
angular.extend(self.data, response);
//Now my view should display my object
//return to chain
return resource().get(user).$promise;
}).then(function(responsePts){
//And THEN update/refresh my view here
angular.extend(self.data, responsePts);
});
};
$q Service promises are integrated with the AngularJS framework and its digest cycle.
$q.when(value)
Wraps an object that might be a value or a (3rd party) then-able promise into a $q promise. This is useful when you are dealing with an object that might or might not be a promise, or if the promise comes from a source that can't be trusted.
-- AngularJS $q Service API Reference - $q.when

Related

Getters and Setters in AngularJS

confirm("Ohhh, hello there, is it Ok to click Cancel?");
I think that this is, basically, a question about CRUD on Angular. I'm kind of confused about getters and setters, mainly because Angular do almost all the job in getting and setting things because of its two way data binding. I want to know what's the best scalable way to create getters and setters so I wont need to modify my functions in the future.
On the first Arrangement, I'm trying to be as simple as I can be, but I feel uncomfortable in getting and getting to set.
Arrangement 01:
$scope.getData = function(){
$http.get(url + '/data')
.then( function (res) {
return res.data; } );
};
$scope.setData = function () {
$scope.data = $scope.getData();
};
$scope.insertData = function (data) {
$http.post(url + '/data', { data: data})
.then( function (res) {
// nothing here. } );
};
On this second Arrangement, however, I'm trying to go directly where I need to. When I fetch data from the server, I'm automagicaly setting my $scope.data to the retrieved data;
Arrangement 02:
$scope.getData = function () {
$http.get(url + '/data')
.then( function (res) {
$scope.data = res.data;
});
};
$scope.insertData = function (data) {
$http.post( url + '/data', { data: data })
.then( function (res) {
$scope.getData(); //To update.
//OR $scope.data.push(res.data);
});
};
Looking further, I've found this on the Angular Docs, but what's the point in using a getter/setter if Angular already do it? Looking into other technologies, it's hard to compare, because Angular has auto-get.
I don't even know how to formulate this question. But, basically, I want to know how could my getters and setters harm my future application and if there's a good way and why to create getters and setters in Angular.
Thanks for any advice.
You good practice is to wrap your logic into Service. You have to know that in Angular, all services are Singleton, there is only a single instance of a Service.
I've made a simple example, by using $q.defer() which is the promise manager from the deferred API.
$q.defer() get 2 methods :
resolve(value) : which resolve our associated promise, by giving her the final value
reject(reason) : which resolve an promise error.
Controller
(function(){
function Controller($scope, $q, Service) {
//Use promise manager
var defer = $q.defer();
///Create our promise
var promise = defer.promise;
$scope.data = [];
var newData = [
{
name:'john',
age: 25
},
{
name: 'toto',
age: 13
}
];
Service.get().then(function(data){
//Retrieve our data
$scope.data = data;
//Set new data to our factory
Service.set(newData);
//Retrieve new data
Service.get().then(function(data){
//Resolve new data
defer.resolve(data);
});
});
//Retrieve new dataset
promise.then(function(data){
$scope.data = data;
})
}
angular
.module('app', [])
.controller('ctrl', Controller);
})();
Service
(function(){
function Service($q){
var data = [0,1,2,3,4];
function set(value){
data = value;
}
function get(){
return $q(function(resolve){
//Simulate latency
setTimeout(function(){
//Resolve our data
resolve(data);
}, 1000);
});
}
return {
get: get,
set: set
};
}
angular
.module('app')
.factory('Service', Service);
})();
HTML
<body ng-app='app' ng-controller="ctrl">
<pre>{{data}}</pre>
</body>
So, you can set some data by using the service, and retrieve it when you want. Don't forget that service is singleton.
You can see the Working Plunker
In JavaScript you typcially don't use getters and setters like in OOP languages, especially because you do not have a notion of privateness (so anyone can access your fields). ES5 has getters and setters, but it also adds this missing capabilities of hiding implementation details. In case you want getters and setters for additional logic in your AngularJS app, you could simply define additional fields which are updated using $watch.
Furthermore you solution with sending an HTTP request on every change is a it of an overhead if you do this per field. What you instead to is writing directly to fields.
While e.g. WPF/C# requires you to define setters to raise OnPropertyChanged, you don't need this in AngularJS. Everything that you write in AngularJS will automatically trigger a so-called $digest cycle, where it checks for changes that have been made. It will then automagically update your user interface, give that you use template bindings or ng-model directives.
If you think like pure Javascript, is basic the same logic, what angular does is create modules for you to use the best practice, so it is easy to use them.
function DataService($http) {
this.get = function() {
return $http.get(...);
}
this.create = function(newData) {
return $http.post(...);
}
..
}
and using angular, like Ali Gajani sayd, you basically can do this,
angular.module('myApp').service('DataService', ['$http', DataService]);
or with a factory style
function DataService($http) {
var myPrivateVariable = "something";
function get() {
return $http.get(...);
}
...
// expose them public
return {
get: get
};
}
angular.module('myApp').factory('DataService', ['$http', DataService]);

AngularJS: the $q.defer() cannot be shared by factory methods

In my webapp, I write a factory methods for serving ajax calls and returning promises by using the $q service. As you can probably tell, I am still on the learning curve of using AngularJS; I found something interesting on the $q.defer() object, which cannot be shared by factory methods. I write the following fake ajax calls in a factory component (plnker here):
(function() {
'use strict';
angular.module('testAjax')
.factory('AjaxPromiseService', AjaxPromiseService);
AjaxPromiseService.$inject = ['$q', '$timeout'];
function AjaxPromiseService($q, $timeout) {
//var deferred = $q.defer(); //cannot be claimed for sharing here
var methodObj = {getDummyData : getDummyData,
getName: getName
};
return methodObj;
/******************** implementations below **********/
function getDummyData() {
var data = {"data" : "dummy data from ajax!"};
var deferred = $q.defer();
$timeout(function() {
deferred.resolve(data);
}, 3000); //3 seconds
return deferred.promise;
} //getDummyData
function getName() {
var deferred = $q.defer();
$timeout(function() {
deferred.resolve({"name": "my name is john doe!"});
}, 2000); //2 seconds
return deferred.promise;
} //getName
}
}());
In my controller, I have the following:
(function() {
'use strict';
angular.module('testAjax', ['ui.router', 'Constants'])
.controller('testController', testController);
testController.$inject = ['$log', 'AjaxPromiseService', '$http', '$q', 'URL_CONFIGS'];
function testController($log, AjaxPromiseService, $http, $q, URL_CONFIGS) {
$log.info('in the testController');
var vm = this;
vm.message = URL_CONFIGS.home;
vm.getData = function() {
AjaxPromiseService.getDummyData().then(function(data) {
vm.message += data.data;
//$log.info($q);
}).catch(function(err) {
$log.info('error in getting data');
}).finally(function() {
$log.info('getData is completed');
}); //getDummyData
}; //getData
vm.getName = function() {
AjaxPromiseService.getName().then(function(data) {
vm.message += data.name;
//$log.info($q);
}).catch(function(err) {
$log.info('error in getting data');
}).finally(function() {
$log.info('getData is completed');
}); //getDummyData
}; //getName
}
}());
In my template, I have the following two buttons that invoke the above two functions in the controller.
<button class="btn btn-primary" ng-click="contentView.getData()">Get data</button>
<button class="btn btn-primary" ng-click="contentView.getName()">Get name</button>
<strong>Message: {{contentView.message}}</strong>
In the factory AjaxPromiseService component, the var deferred object cannot be shared between the two functions inside the factory, and I have to define a deferred object for each function, otherwise it wouldn't work. So I was wondering why deferred cannot be shared between methods in a factory?
Why deferred cannot be shared between methods?
Because a Deferred object is linked to the promise it resolves. Each promise needs its own one. If you share a single deferred, each method would return the same promise.
See also What are the differences between Deferred, Promise and Future in JavaScript?.
actually you can share the deferred object. Just think about it not as a service, but a simple JS object. One deferred object can be resolved only once, that's done for purpose. In your AjaxPromiseService you obviously need two differend deferreds, because you resolve them with different data.
for example, $http.post() every time returns different deferred objects
sharing of one deferred between several functions is useful when your deferral can be resolved from different sources (for example you try to get some data simultaneously from localStorage cache, http source and some WebWorker, that is calculating this data)
If i understand you correctly you want to reuse the same object created by $q.defer().
$q.defer() returns an object that will be resolved at some point in the future by calling the .resolve method. So basically it represents a certain action that will be completed at some point in the future.
You cannot share the same promise object for multiple actions that complete in the future.
See also the link in Bergi's anwser.
Also you plunker is broken because
var methodObj = {getDummyData : getDummyData};
is missing the getName I fixed it in this plunker

$http and factory - how does this pattern work?

Below is the recommended way to get data into a controller from a factory using $http -- according to https://github.com/johnpapa/angularjs-styleguide
What i don't get is how the two success callbacks on $http work (i commented what i think the two callbacks are).
1) What is the point of the first callback?
2) Where does vm.avengers point to? Is it a reference to another object?
3) is 'data' in the second callback = 'response.data.results' from the first?
4) I'm counting 3 total callbacks chained, is that correct?
P.S. i already know about promises, but want to learn this pattern specifically
The factory
/* recommended */
// dataservice factory
angular
.module('app.core')
.factory('dataservice', dataservice);
dataservice.$inject = ['$http', 'logger'];
function dataservice($http, logger) {
return {
getAvengers: getAvengers
};
function getAvengers() {
return $http.get('/api/maa')
.then(getAvengersComplete)
.catch(getAvengersFailed);
//Callback One
function getAvengersComplete(response) {
return response.data.results;
}
function getAvengersFailed(error) {
logger.error('XHR Failed for getAvengers.' + error.data);
}
}
}
The Controller
function Avengers(dataservice, logger) {
var vm = this;
vm.avengers = [];
activate();
function activate() {
return getAvengers().then(function() { //Callback 3
logger.info('Activated Avengers View');
});
}
function getAvengers() {
return dataservice.getAvengers()
.then(function(data) { //Callback 2
vm.avengers = data;
return vm.avengers;
});
}}
The point of this first callback is to do any manipulation with the data prior to it entering into the app and to actually pull the useful data out of the http response object.
vm.avengers is declared at the top of your controller. It's using the "controller as" syntax and is being put on a reference to the "this" object of the controller. You're ultimately using vm.avengers to access the data in the view.
Correct.
HTTP Call -> getAvengersComplete -> getAvengers, so correct 3 callbacks.

Understanding Controller and Service interaction

With AngularJS, I'm trying to implement a simple service that returns a list of clients from a REST service which is then available in a controller.
I am stuck in figuring out how to properly pass data to the controller. Below is my service and it pulls the data just fine. I've verified that the data is there
app.service('clientsService', ['$http', function ($http) {
var serviceBase = 'http://localhost:56879/api/';
this.getClients = function () {
return $http.get(serviceBase + 'clients').then(function (results) {
console.log(results.data);
return results.data;
});
};
}]);
Next I attempt to use this in a controller
app.controller('clientsController', ['$scope', 'clientsService', function ($scope, clientsService) {
this.clients = clientsService.getClients();
console.log(this.clients);
}]);
In this controller, this.clients doesn't contain the data, it just contains a try-catch block
Object {then: function, catch: function, finally: function}
catch: function (a){return this.then(null,
finally: function (a){function b(a,c){var d=e();c?d.resolve(a):d.reject(a);return d.promise}function d(e,g){var f=null;try{f=(a||c)()}catch(h){return b(h,!1)}return f&&P(f.then)?f.then(function(){return b(e,g)},function(a){return b(a,!1)}):b(e,g)}return this.then(function(a){return d(a,!0)},function(a){return d(a,!1)})}
then: function (b,g,h){var m=e(),u=function(d){try{m.resolve((P(b)?b:c)(d))}catch(e){m.reject(e),a(e)}},F=function(b){try{m.resolve((P(g)?g:d)(b))}catch(c){m.reject(c),a(c)}},v=function(b){try{m.notify((P(h)?h:c)(b))}catch(d){a(d)}};f?f.push([u,F,v]):k.then(u,F,v);return m.promise}
__proto__: Object
I can't understand quite yet what it is that I've done incorrectly to actually pass data from the service to the controller.
That is because getClients method returns a promise, not data. The promise resolves to return the data in the callback. The methods that you are seeing in the console is of that of the promise object returned by the service method. So you should register a callback to then method of the promise:-
var _that = this;
clientsService.getClients().then(function(data) { //Runs when promise is resolved
_that.clients = data;
}).catch(function(){ //<-- Runs if the promise is rejected
});
You could look at the ngResource service which is really easy to use. It is based on $http but with a little more abstraction :
https://docs.angularjs.org/api/ngResource/service/$resource

Getting a Filter to handle a Promised Service

I have already started to rework this code to operate synchronously, but out of curiosity and a desire to support both means, I need some help understanding how to get a filter to jive with a promise. As some other posts mention a filter seems to just resolve to {} from a promise.
Basic Pattern
Here's a breakdown:
Define a service in the module that returns a promise instead of an object
module.factory('promisedSvc', ['$http', function($http) {
var httpPromise = null,
servicePromise = null,
service = {},
dataSet = {};
var httpPromise = $http.get('somedata.json').success(function(data) {
dataSet = data;
});
servicePromise = httpPromise.then(function(){
service.getData = function(key) {
return dataSet[key];
};
service.addData = function(key, value) {
dataSet[key] = value;
};
return service;
});
/*
In actuality I proxied the service methods onto the promise because
I didn't want consumers of the service to have to deal with it being
a promise. There is the caveat of setting properties on a class I
don't own (property collisions), a risk I'm okay taking, but YMMV
Commented out proxies
servicePromise.getData = function(key) {
return this.then(function(svc){
return svc.getData(key);
});
};
servicePromise.addData = function(key, value) {
this.then(function(svc){
svc.addData(key, value);
});
};
*/
return servicePromise;
}]);
Controllers can handle this promisedSvc fine, you just get the promise injected into the controller and then use the then function on the promise to wrap the setting of a $scope property to the function call on the eventual service object: getData(key) or setData(key, value). Alternately you can just treat it as normal if you proxied the functions onto the promise like in the commented out block.
Filters do not seem to inherently handle promises like $scope does. I am looking for a way to get the filter to inject the promisedSvc and be able to call getData(key) without it resolving to {} because the promise has not resolved yet. Below is an example of what does not work:
module.filter('svcData', ['promisedSvc', function(promisedSvc) {
return function(input) {
return promisedSvc.then(function(svc) {
var value = svc.getData(input);
return value;
});
};
}]);
So is there a way to write the filter to be able to resolve the value?
Use Case
That is the simplified pattern of what I am trying to achieve. For those curious, my actual use case is to pre-fetch i18n/l10n resource bundle information so I can localize all the text in my application. The pre-fetch could all be in the Javascript environment (attached to some already loaded global or in a provider), but we also have scenarios with database-stored Resource Bundles so I needed a version of code that can pre-fetch all the information from the server via AJAX.
It's not exactly what I'm looking for, but at least to document a workaround:
It's possible to use a function on the $scope instead of a filter.
module.factory('promisedSvc', ['$http', '$rootScope', function($http, $rootScope) {
var httpPromise = null,
servicePromise = null,
service = {},
dataSet = {};
var httpPromise = $http.get('somedata.json').success(function(data) {
dataSet = data;
});
servicePromise = httpPromise.then(function(){
service.getData = function(key) {
return dataSet[key];
};
service.addData = function(key, value) {
dataSet[key] = value;
};
//Here is the addition to setup the function on the rootScope
$rootScope.svcData = function(key) {
return service.getData(key);
};
return service;
});
return servicePromise;
}]);
And then in a template instead of {{ 'key1' | svcData }} you would use {{ svcData('key1') }}
I tested that if you delay the promises resolution (for example setup a wait in the $http.success) that the impact is the page loads, but the values from the svcData function will only populate into the template once the promises resolve.
Still would be nice to accomplish the same with a filter if possible.

Categories