Process promise returned data using decorator or interceptor in AngularJS - angularjs

I have a service that does an http request to save some data. When the data comes from the backend I am doing some manipulation on the data and then return it so that controllers can use them. Something like:
public savePerson = (person: Model.IPerson): ng.IPromise<Model.IMiniPerson> => {
return this.api.persons.save({}, person).then((savedPerson) => {
this.enrichWithLookups(savedPerson);
var miniPerson = new Model.MiniPerson();
angular.extend(miniPerson, savedPerson);
miniPerson.afterLoad();
this.persons.unshift(miniPerson);
this.notifyOfChanges();
return miniPerson;
});
}
In order to clean up the code a bit and make it more testable I wanted to remove the private manipulation functions into decorating/intercepting services. Problem is I do not know how to hook on the promise data before the success function is executed and after it is returned.
For example enrichWithLookups must be applied first just after the data arrives and not after the miniPerson is returned.

you can create a local promise and call the "resolve" method when you have completed your operations on the http response. Look at the code down here:
public savePerson = (person: Model.IPerson): ng.IPromise<Model.IMiniPerson> => {
var waiter = $q.defer();
this.api.persons.save({}, person).then((savedPerson) => {
this.enrichWithLookups(savedPerson);
var miniPerson = new Model.MiniPerson();
angular.extend(miniPerson, savedPerson);
miniPerson.afterLoad();
this.persons.unshift(miniPerson);
this.notifyOfChanges();
waiter.resolve(miniPerson);
});
return waiter.promise;
}
I've wrote the code directly with angularjs, but I think that you can easily adapt it to fit your needs.
Bye.

Related

How to call the same api multiple times in Express Route?

I'm working on a Node app with Express. I'm chaining several http calls to data api's, each dependent on the previous req's responses.
It's all working except the last call. The last call needs to happen multiple times before the page should render.
Searching has turned up excellent examples of how to chain, but not make a call to the same API (or HTTP GET, data endpoint, etc.) with different params each time.
I'm trying to do something like this: Using a generator to call an API multiple times and only resolve when all requests are finished?
var getJSON = (options, fn) => {
.....
}
router.route("/")
.get((req, res) => {
var idArray = [];
var results = [];
getJSON({
.... send params here, (result) => {
//add response to results array
results.push(result);
//create var for data nodes containing needed id params for next call
let group = result.groupsList;
//get id key from each group, save to idArray
for(i=0;i<groups.length;i++){
idArray.push(groups[I].groupId);
}
//use id keys for params of next api call
dataCallback(idArray);
});
function dataCallback(myArray){
// number of ID's in myArray determine how many times this API call must be made
myArray.forEach(element => {
getJSON({
.... send params here, (result) => {
results.push(result);
});
// put render in callback so it will render when resolved
}, myRender());
};
function myRender() {
res.render("index", { data: results, section: 'home'});
}
})
I learned the problem with the above code.
You can call functions that are outside of the express route, but you can't have them inside the route.
You can't chain multiple data-dependent calls, not in the route.
Anything inside route.get or route.post should be about the data, paths, renders, etc.
This means either using an async library (which I found useless when trying to build a page from multiple data sources, with data dependent on the previous response), or having an additional js file that you call (from your web page) to get, handle and model your data like here: Using a generator to call an API multiple times and only resolve when all requests are finished You could also potentially put it in your app or index file, before the routes.
(It wasn't obvious to me where that code would go, at first. I tried putting it inside my router.post. Even though the documentation says "Methods", it didn't click for me that routes were methods. I hadn't really done more than very basic routes before, and never looked under the hood.)
I ended up going with a third option. I broke up the various API calls in my screen so that they are only called when the user clicks on something that will need more data, like an accordion or tab switch.
I used an XMLHttpRequest() from my web page to call my own front-end Node server, which then calls the third party API, then the front-end Node server responds with a render of my pug file using the data the API provided. I get html back for my screen to append.
In page:
callFEroutetoapi(_postdata, _route, function (_newdata){
putData(_newdata);
});
function putData(tData){
var _html = tData;
var _target = document.getElementById('c-playersTab');
applyHTML(_target, _html);
}
function callFEroutetoapi(data, path, fn){
//url is express route
var url = path;
var xhr = new XMLHttpRequest();
console.log('data coming into xhr request: ', data);
//xhr methods must be in this strange order or they don't run
xhr.onload = function(oEvent) {
if(xhr.readyState === xhr.DONE) {
//if success then send to callback function
if(xhr.status === 200) {
fn(xhr.response);
// ]console.log('server responded: ', xhr.response);
}
else {
console.log("Something Died");
console.log('xhr status: ', xhr.status);
}
}
}
xhr.onerror = function (){console.log('There was an error.', xhr.status);}
xhr.open("POST", url, true);
xhr.setRequestHeader("Content-Type", "application/json;charset=UTF-8");
xhr.send(JSON.stringify(data));
}
It adds an extra layer, but was necessary to show the latest, frequently changing data. It's also reusable which is better for a multiscreen web app. If there were fewer views (completely different screens and co-dependent datasets), a more centralized model.js file mentioned above would work better.

Observable patterns for caching and defered data retrieval

I'm trying to create a caching function in angular using RxJS Observable. Originally I've created this method using angularjs $q's deferred promise. Observables and RxJS are new to me and I find this method of working still somewhat confusing.
This is my current implementation of a getOrCreate caching function. Retrieve a single value for a key from storage (this.get()) and if it's not in there you retrieve it elsewhere (fetcher).
Assume fetcher is a slower data source than this.get(). Multiple requests for the same key could fire while we're still retrieving from this.get() so I put in an aggregator: only a single observable is created for multiple requests of the same key.
protected observableCache : {[key: string] : Observable<any>} = {};
get<T>(key : string): Observable<T> { /* Async data retrieval */ }
getOrCreate<T>(key : string, fetcher: () => Observable<T>) : Observable<T> {
const keyHash = this.hash(key);
// Check if an observable for the same key is already in flight
if (this.observableCache[keyHash]) {
return this.observableCache[keyHash];
} else {
let observable : Observable<T>;
this.get(key).subscribe(
// Cache hit
(result) => { observable = Observable.of(result); },
// Cache miss. Retrieving from fetching while creating entry
() => {
fetcher().subscribe((fetchedResult) => {
if(fetchedResult) {
this.put(key, fetchedResult);
}
observable = Observable.of(fetchedResult);
});
}
);
// Register and unregister in-flight observables
this.observableCache[keyHash] = observable;
observable.subscribe(()=> {
delete this.observableCache[this.hash(key)];
});
return observable;
}
}
This is my current version of that code but it doesn't look like I'm properly handling async code:
Observable will be returned before it's instantiated: return observable fires before observable = Observable.of(result);
There's probably a much better pattern of aggregating all requests for the same key while this.get() is still in-flight.
Can someone help with finding the Observer patterns should be used?
I think this might work. Rewritten as:
getOrCreate<T>(key : string, fetcher: () => Observable<T>) : Observable<T> {
const keyHash = this.hash(key);
// Check if an observable for the same key is already in flight
if (this.observableCache[keyHash]) {
return this.observableCache[keyHash];
}
let observable : ConnectableObservable<T> = this.get(key)
.catch(() => { // Catch is for when the source observable throws error: It replaces it with the new Rx.Observable that is returned by it's method
// Cache miss. Retrieving from fetching while creating entry
return this.fetchFromFetcher(key, fetcher);
})
.publish();
// Register and unregister in-flight observables
this.observableCache[keyHash] = observable;
observable.subscribe(()=> {
delete this.observableCache[keyHash];
});
observable.connect();
return observable;
},
fetchFromFetcher(key : string, fetcher: () => Observable<T>) : Observable<T> {
// Here we create a stream that subscribes to fetcher to use `this.put(...)`, returning the original value when done
return Rx.Observable.create(observer => {
fetcher().subscribe(fetchedResult => {
this.put(key, fetchedResult);
observer.next(fetchedResult);
},
err => observer.error(err),
() => observer.complete())
});
}
Explanations:
Observables are very different from promises. They are to work with async stuff, and there are some similarities, but they are quite different
As this.get(...) seems asynchronous, your let observable won't get filled until it yields a value, so when you assign it to your cache it's normal that's null.
A great thing about observables (and the main difference with promises) is that you can define a stream before anything gets executed. In my solution, nothing gets called until I call observable.connect(). This avoids so many .subscriptions
So, in my code I get the this.get(key) stream, and tell it that if it fails (.catch(...)) it must fetch the result, but once that's fetched then put it into your cache (this.put(key, fetchedResult)
Then I publish() this observable: This makes it so it behaves more like promises do, it makes it "hot". This means that all subscribers will get the values from the same stream, instead of creating a new stream that starts from 0 everytime one subscribes to it.
Then I store it in the observable pool, and set to delete it when it finishes.
Finally, I .connect(). This is only done if you publish() it, it's the thing that actually subscribes to the original stream, executing everything you want.
To make it clear, because this is a common error coming from Promises, in angular if you define a stream as:
let myRequest = this.http.get("http://www.example.com/")
.map((result) => result.json());
The request it's not sent yet. And everytime you do myRequest.subscribe(), a new request to the server is made, it won't reuse the "first subscription" result. That's why .publish() is very useful: It makes that when you call .connect() it creates a subscription that triggers the request, and will share the last result received (Observables support streams: Many results) with all incoming subscriptions to the published observable.

Angular firebase fetching single item has race conditions

I'm using angular and firebase together and I have a products array which i'm storing in my rootscope, though it takes time to load the items.
My issues is that when I go to this page for example directly:
http://localhost/product/greyish-sports-shoes
If I go to the home page, the products load after 2 seconds.. and then only if I click on the product link it takes me to it, and it'll work because products have already been loaded.
It goes to the shoeService which contains the products array, but the items are still not loaded, so it cannot find the product by its slug.
That's the code I use in my run method.
var ref = firebase.database().ref().child('products');
$rootScope.shopProds = $firebaseArray(ref);
My shoeService factory:
function shoeFactory($rootScope) {
this.service = {};
this.service.store = new Store($rootScope.shopProds);
this.service.cart = new Cart();
return this.service;
}
It is important to realize that the $firebaseArray service returns an array that is initially empty. The array is populated asynchronously after the data is returned from the server.
Use the promise returned by the $loaded method attached to the array:
function shoeFactory($rootScope) {
this.service = {};
this.service.storePromise = $rootScope.shopProds.$loaded()
.then ( (shopProds) => {
return new Store(shopProds);
});
this.service.cartPromise = this.service.storePromise
.then ( () => {
return new Cart();
}).catch( (error) => {
console.log("ERROR in shoeFactory");
throw error;
});
return this.service;
}
To avoid race conditions, the code needs to use promises to chain operations.

mapping the response to corresponding request

I am making $http request to multiple environment and processing after I get all the responses. I am using the code below:
$q.all(Object.keys($rootScope.envs).map(request)).then(function(res){
var results = {};
for (var env in res) {
results[env] = res[env].data;
}
}, function(err){
console.error(err);
});
function request(env) {
return $http.get(callService.getDomainUrl()+'/'+$rootScope.envs[env]+ '/hosts.json');
}
The above code works fine, but the results object looks like below:
{
0: {data:{}},
1: {data:{}},
2: {data:{}},
3: {data:{}}
}
I want the corresponding response for each key and the results should be like
{
env1: {data:{//data for env1}},
env2: {data:{//data for env2}},
env3: {data:{//data for env3}},
env4: {data:{//data for env4}},
}
How to map the corresponding response to the key? Please let me know how to get this as this is asynchronous request. Should I have something from the API to know which env the API is coming from?
I think the simplest way would be to push the result handling into the request function, that way you still have the 'env' value in scope.
var results = {};
$q.all(Object.keys($rootScope.envs).map(request)).then(function(res){
// Do something with 'results' here.
}, function(err){
console.error(err);
});
function request(env) {
return $http.get(callService.getDomainUrl()+'/'+$rootScope.envs[env]+ '/hosts.json')
.then(function(res) { results[env] = res.data; return env; });
}
Another option would be to replace my return env with return [env, res.data] and then you can go back to creating the results object as in your original code.
The important thing here is to remember you can handle the $http.get promises individually as well as using the promises from the call to then in $q.all.

Multiple $http calls doesn't return in the right order

I made a for loop and inside I make a $http call to my API.
The for loop makes the calls in a specific order, but how I get the reponse is totally messed up..
This is my code:
for (var i = 0; i < amountOfEntries; i++) {
var _imageId = NieuwsService.nieuws[i].image;
if (_imageId != "") {
var uriString = "Web/Lists/getbytitle('Afbeeldingen%20voor%20nieuwsberichten')/Items(" + _imageId + ")/File";
NieuwsService.createRequest(requestUrl, baseUrl, uriString).then(function (response) {
var _parser = new DOMParser();
var _xmlData = _parser.parseFromString(response.data, "text/xml");
var _entry = _xmlData.getElementsByTagName("entry");
var _imageUrl = "http://sharepoint" + _entry[0].getElementsByTagNameNS("*", "ServerRelativeUrl")[0].childNodes[0].nodeValue;
//Display
$('#imageList').append("<li><a href='#'>" + _imageUrl + "</a></li>");
NieuwsService.images.push(_imageUrl);
})
}
}
var _createRequest = function (requestUrl, baseUrl, uriString) {
var promise = $http.get(requestUrl, {
params: {
"baseUrl": baseUrl,
"uriString": uriString
}
})
return promise;
}
NieuwsService.createRequest = _createRequest;
return NieuwsService;
So the question is, how do I get the responses in the order I make the calls?
You can create and array of promises, then use $q.all that will resolve when all the requests are done, and you will have the result of each one in the same position where you added the promise.
$http is ASYNC by design, it means that each HTTP will go out in the same order but the response depends on many cases (network,dns, server response time ext...)
you should design your code in a way that it will take that into consideration.
You can use promise
As #RonnieTroj pointed out, the ajax calls are asynchronous and there is no defined order in that respect.
However, if you want to chain api calls such that you get responses in a specific order, then your only option is nesting calls in the following way:
call1.then(function(data1) { //first call
call2.then(function(data2) {//second call after first call completes
//and so on
})
})
However, the issue with this method is that it will take more time because you are essentially making the calls sequential in nature(one call executes, then another instead of parallel async calls.)

Resources