Enriching javascript objects contained in an angular promise with behavior/methods - angularjs

I have the following angular service. It retrieves a array of json messages from the backend and I want to be able to convert those messages (plain js objects with no behavior) into object of type Message (see below).
Ideally I'd like to transform the data from the unresolved ng promise and passing each json message into the Message constructor as follows:
new Message(jsonMsg);
How can I achieve that?
Here is my service:
function Message(data) {
var defaults = {
sender: null,
recipient: null,
messageRead: false
};
angular.extend(this, defaults, data);
}
Message.prototype.getCounterparty = function (user) {
if (!this.sender) return null;
return (user.id !== this.sender.id) ? this.sender : this.recipient;
};
Message.prototype.isSender = function (user) {
return user.id === this.sender.id;
};
Message.prototype.isRecipient = function (user) {
return user.id === this.recipient.id;
};
...
findLatestsMessages: function (otherId) {
return $http.get('/api/message/find-latest-messages' + otherId);
}

You have to use a factory :
angular.factory('Message', [function() {
var Message= function(data) {
var defaults = {
sender: null,
recipient: null,
messageRead: false
};
angular.extend(this, defaults, data);
}
Message.prototype.isSender = function (user) {
return user.id === this.sender.id;
};
// ...
return Message;
}]);
Then you inject your factory in your controller
// ...
angular.controller('ctrl', ['$http', '$scope', 'Message', function($http, $scope, Message) {
$scope.messages = []
$scope.findLatestsMessages = function (otherId) {
return $http
.get('/api/message/find-latest-messages' + otherId)
.then(function(response) {
// and you create your messages when your promise has resolved
$scope.messages = response.data.map(function(m) {
return new Message(m);
}
});
}]);

// Assuming json response looks like
// [
// {sender: "John Doe", recipient: "Jane Doe", messageRead: true},
// {sender: "John Doe", recipient: "Jane Doe", messageRead: true}
// ]
findLatestsMessages: function (otherId) {
return $http
.get('/api/message/find-latest-messages' + otherId)
.then(function (response) {
var messages = response.data;
return messages.map(function (message) {
return new Message(message);
});
});
}
USAGE:
findLatestsMessages(someId).then(function (messages) {
// messages is an array of Message objects.
});

Then I will no longer have a promise to deal with on the calling-side?
You can return an object reference that later gets resolved to what you want. But beware, an XHR created by $http always resolves asynchronously, sometime after it is created.
As example:
function MessageResponseObject(url) {
var message = {};
var httpPromise = http.get(url);
//transform response
var derivedPromise = httpPromise.then( function(response) {
//save data to object
message.data = response.data;
//return message reference for chaining
return message;
});
//attach promise to object
message.$promise = derivedPromise;
//
return message;
});
$scope.message = MessageResponseObject(url);
You can use the response object in your template without a promise.
<div ng-repeat="item in message.data">
{{item}}
</div>
This will work in the UI because the AngularJS framework puts a $watchCollection on message.data. It checks the value after each digest cycle; and when the data gets resolved, it places the data on the DOM.
Clients in the controller that need to wait can use the attached promise.
$scope.message.$promise.then (function onFulfilledHandler(message) {
//do subsequent actions
}).catch (function onRejected(errorResponse) {
//do error actions
});
It is important to realize that using method immediately returns an empty reference. Once the data is returned from the server the existing reference is populated with the actual data. This is a useful trick since usually the resource is assigned to a model which is then rendered by the view. Having an empty object results in no rendering, once the data arrives from the server then the object is populated with the data and the view automatically re-renders itself showing the new data. This means that in most cases one never has to write a callback function for the action methods.1
Best practice
Return the promise as a property of the object that resolves to the object. This makes it easy to use in resolve section of $routeProvider.when() to defer view rendering until the resource(s) are loaded.
For more information on this methodology, see AngularJS ngResource $resource API Reference.

Related

How do I make a promise resolve as an object in the view?

I'm trying to wrap a third party library to return an object that resolves into an object that can be displayed in the view, similar to how $resource() works. I'm aware that I can manually do .then() on the promise and then set the value, but I wanted the result to seamlessly return similar to how I can do:
this.Value = $resource("/someresource").get();
How would I change the below SomeThirdPartyFunction() to return an object that resolves in the view.
Here's an example of what I'm trying to do:
angular.module('testApp', []).controller('TestController', function ($timeout, $q) {
var TestController = this;
var SomeThirdPartyFunction = function () {
var Deferred = $q.defer();
var Promise = Deferred.promise;
$timeout(function () {
Deferred.resolve("abcd");
}, 3000);
return Promise;
};
TestController.Value = SomeThirdPartyFunction();
/* I don't want to do this:
SomeThirdPartyFunction().then(function(Value) {
TestController.Value = Value;
});*/
});
And here's a plunker: https://plnkr.co/edit/HypQMkaqXmFZkvYZXFXf?p=preview
Every example I've seen using promises just wraps $http calls, but I haven't seen any examples of calling third party libraries that return promises that resolve into objects.
From the AngularJS document:
It is important to realize that invoking a $resource object method
immediately returns an empty reference (object or array depending on
isArray). Once the data is returned from the server the existing
reference is populated with the actual data.
So instead of return a promise, you can do something like this:
var SomeThirdPartyFunction = function() {
var getAComplextObject = function() {
return {
number: 42,
method: function() {
return this.number + 1;
}
};
};
var returnValue = {};
$timeout(function() {
console.log("Resolved!");
Object.assign(returnValue, getAComplextObject());
}, 1000);
return returnValue;
};
You can wrap it in a promise and make the promise part of the return value, doing that you can make it thenable (aka a Promise)
Under-the-hood, $resource uses angular.copy:
function myResourceGet(params) {
var emptyObj = {};
emptyObj.$resolved = false;
emptyObj.$promise = $http.get(url, {params:params})
.then(function(response) {
angular.copy(response.data, emptyObj);
}).finally(function() {
emptyObj.$resolved = true;
});
return emptyObj;
}
From the Docs:
angular.copy Overview
Creates a deep copy of source, which should be an object or an array.
If a destination is provided, all of its elements (for arrays) or properties (for objects) are deleted and then all elements/properties from the source are copied to it.
The $resource service only works when the data is an object or an array. It does not work with primitives such as a string or number.

AngularJs return async factory?

I've read many answers to this question but I just don't get it. Where does the promise go? I made a simple factory with an async call to a cloud database:
app.factory('asyncFactory', function() {
let toController = function() {
firebase.database().ref('en').once('value') // get the array from the cloud database
.then(function(snapshot) { // take a snapshot
console.log(snapshot.val()); // read the values from the snapshot
return snapshot.val(); // this returns later
});
return 47 // this returns immeadiately
};
return {
toController: toController // why is this necessary?
}
});
I call it from my controller:
$scope.words = asyncFactory.toController();
console.log($scope.words);
Here's the response:
As you can see, 47 returns to the controller immediately. If I comment out return 47 then the factory returns undefined. Later the async data logs but doesn't return to the controller. I use promises every day but I can't figure out where the promise goes.
Second question: do I need the line toController: toController ? Can I get rid of it?
Thanks!
To use the results from the firebase call in the controller, the factory method needs to return a promise:
app.factory('asyncFactory', function($q) {
return {
toController: toController
};
function toController() {
var es6promise = firebase.database().ref('en').once('value');
var qPromise = $q.when(es6promise)
.then(function(snapshot) { // take a snapshot
console.log(snapshot.val()); // read the values from the snapshot
return snapshot.val(); // this returns later
});
return qPromise;
};
});
Because the firebase .once method returns an ES6 promise, that promise needs to be brought into the AngularJS framework by converting it to a $q Service promise with $q.when. Only operations which are applied in the AngularJS execution context will benefit from AngularJS data-binding, exception handling, property watching, etc.
In the controller, use the .then method to extract the data after it returns from the server:
var qPromise = asyncFactory.toController();
qPromise.then(function(data) {
console.log(data)
$scope.words = data;
});
The factory function immediately returns a promise. When the data arrives from the server, the data will be placed on $scope.
Well toController is eating the promise for itself. ( whenever you call .then(), it means you are waiting for promise),
Try this
app.factory('asyncFactory', function() {
let toController = function() {
var deferred = $q.defer();
firebase.database().ref('en').once('value') // get the array from the cloud database
.then(function(snapshot) { // take a snapshot
console.log(snapshot.val()); // read the values from the snapshot
return deferred.resolve(snapshot.val()); // this returns later
});
//return deferred.resolve(47) // this returns immeadiately
};
return {
toController: toController // why is this necessary?
}
});
If you don't want this line
return {
toController: toController // why is this necessary? }
app.factory('asyncFactory', function() {
return {
var deferred = $q.defer();
firebase.database().ref('en').once('value') // get the array from the cloud database
.then(function(snapshot) { // take a snapshot
console.log(snapshot.val()); // read the values from the snapshot
return deferred.resolve(snapshot.val()); // this returns later
});
//return deferred.resolve(47) // this returns immeadiately
};
})

AngularJS Promises: Put promise in Factory so it is globally accessible and *editable*

I'm trying to pull data from an external JSON file and display it for the user to see. Through various actions, the user would then be able to change the data returned from the JSON file, without writing those changes to the file (in this example, incrementing values by one by clicking on a div). I've created a promise service that successfully pulls the data and displays it. I can even get it so the data can be changed in individual controllers.
This is where I get stuck: I cannot find a way to make any changes to the data in the PromiseService, so changes cannot propagate globally. How do I make it that any change in the promise data at the controller level will be reflected in the PromiseService and, thus, reflected in any data binding in the app? I'm new to promises, so I'm open to a completely different approach.
Plunker
HTML:
<body ng-app="pageApp" ng-controller="pageCtrl" nd-model="items">
{{items}}
<div class="button" ng-controller="buttonCtrl" ng-click="incrementValues()">
Click to increment:
<br>{{items}}
</div>
</body>
PromiseService:
pageApp.factory('PromiseService', function($http) {
var getPromise = function() {
return $http.get('items.json').then(function(response) {
return response.data;
});
};
return {
getPromise: getPromise
};
});
Button Controller (Page Controller in Plunker):
pageApp.controller('buttonCtrl', function($scope, PromiseService) {
$scope.incrementValues = function()
{
PromiseService.getPromise().then(function(data) {
$scope.items = data;
for(var i = 0; i < data.items.length; i++)
{
data.items[i]['value']++;
}
}).catch(function() {
});
};
});
The incrementValues function works successfully the first time, but each consecutive click re-pulls the promise and resets the data. To sum up: how do I reflect the incremented values in the PromiseService, as opposed to local variables?
You could add to your factory a private property where you store the items. Then create 3 different methods to update and access to that property.
pageApp.factory('PromiseService', function($http) {
var items = {}; // [] in case it is an array
var updateData = function(updatedData){
items = updatedData;
}
var getUpdateData = function(){
return items;
}
var getPromise = function() {
return $http.get('items.json').then(function(response) {
items = response.data;
return response.data;
});
};
return {
getPromise: getPromise,
updateData : updateData,
getUpdateData : getUpdateData
};
});
pageApp.controller('buttonCtrl', function($scope, PromiseService) {
$scope.items = [];
//You should call this method to retrieve the data from the json file
$scope.getData = function(){
PromiseService.getPromise().then(function(data) {
$scope.items = data;
}).catch(function() {
});
}
$scope.incrementValues = function(){
for(var i = 0; i < $scope.items.length; i++){
$scope.items[i]['value']++;
}
PromiseService.updateData($scope.items); //This could be skipped in case you do not want to 'store' these changes.
};
});
Then in others controller you could use the same service to retrieve the updated Data like this:
$scope.items = PromiService.PromiseService();
In the future you could also create a new method to update the json itself instead of stored internally
Your function creates a new $http call every time it's called, and thus returns a new promise, encspsulating new data, every time it's called.
You need to return the same promise every time:
var thePromise = $http.get('items.json').then(function(response) {
return response.data;
});
var getPromise = function() {
return thePromise;
};

how to retrieve ajax response value from service?

I am using 2 service in controller
First Service is to get AjaxResponse where logic to fetching the response is mentioned
The second Service calls the first service to make Http request and get result and then, in turn, return it to the controller
Ctrl Dependency injected firstService,secondService
this.getService = secondService.getData(param);
First Service--> firstService
this.httpResponse(param){
var re = $http.get(param);
return re.then(success,fail);
}
function success(data){
return data;
}
function fail(data){
console.log(data);
}
Second Service (Dependency injection of First Service)
function secondService(firstService){
this.getData = function(param){
return firstService.httpResponse(param);
};
}
this.getService is coming as undefined, all the call are going properly.
Even tried the following code:
secondService.getData(param).then(function(data){console.log(data);});
That doesn't help either.
You should chain the promises in these kinds of situations.
First, you define your service. It should contain two distinct functions. As an example, I did a GET and a POST.
angular.module("myApp",[]).factory("SharedServices", function($http) {
return {
getItem: function() {
return $http.get('path/to/api');
},
postItem: function(payload) {
return $http.post('path/to/api', payload);
}
};
});
Then, reference the service in your controller. getItem() will return a promise, where you can use it using .then to call your second service in the success callback.
angular.module("myApp",[]).controller("MainCtrl", function($scope, SharedServices) {
SharedServices.getItem().then(function(response) {
//success, got something
//call the second part
var payload = { myPayload: response.data.someItem };
SharedServices.postItem(payload).then(function(response) {
//success
}, function(response) {
//an error has occurred to the second call--POST
});
}, function(response) {
//an error occurred to the first call--GET
});
});
Used Callback to get the result.It is similar to deferred(promise)

Controlling order of execution in angularjs

I have inherited an angular app and now need to make a change.
As part of this change, some data needs to be set in one controller and then used from another. So I created a service and had one controller write data into it and one controller read data out of it.
angular.module('appRoot.controllers')
.controller('pageController', function (myApiService, myService) {
// load data from API call
var data = myApiService.getData();
// Write data into service
myService.addData(data);
})
.controller('pageSubController', function (myService) {
// Read data from service
var data = myService.getData();
// Do something with data....
})
However, when I go to use data in pageSubController it is always undefined.
How can I make sure that pageController executes before pageSubController? Or is that even the right question to ask?
EDIT
My service code:
angular.module('appRoot.factories')
.factory('myService', function () {
var data = [];
var addData = function (d) {
data = d;
};
var getData = function () {
return data;
};
return {
addData: addData,
getData: getData
};
})
If you want your controller to wait untill you get a response from the other controller. You can try using $broadcast option in angularjs.
In the pagecontroller, you have to broadcast your message "dataAdded" and in the pagesubcontroller you have to wait for the message using $scope.$on and then process "getData" function.
You can try something like this :
angular.module('appRoot.controllers')
.controller('pageController', function (myApiService, myService,$rootScope) {
// load data from API call
var data = myApiService.getData();
// Write data into service
myService.addData(data);
$rootScope.$broadcast('dataAdded', data);
})
.controller('pageSubController', function (myService,$rootScope) {
// Read data from service
$scope.$on('dataAdded', function(event, data) {
var data = myService.getData();
}
// Do something with data....
})
I would change your service to return a promise for the data. When asked, if the data has not been set, just return the promise. Later when the other controller sets the data, resolve the previous promises with the data. I've used this pattern to handle caching API results in a way such that the controllers don't know or care whether I fetched data from the API or just returned cached data. Something similar to this, although you may need to keep an array of pending promises that need to be resolved when the data does actually get set.
function MyService($http, $q, $timeout) {
var factory = {};
factory.get = function getItem(itemId) {
if (!itemId) {
throw new Error('itemId is required for MyService.get');
}
var deferred = $q.defer();
if (factory.item && factory.item._id === itemId) {
$timeout(function () {
deferred.resolve(factory.item);
}, 0);
} else {
$http.get('/api/items/' + itemId).then(function (resp) {
factory.item = resp.data;
deferred.resolve(factory.item);
});
}
return deferred.promise;
};
return factory;
}

Resources