This lesson is for PRO members.

Unlock this lesson NOW!
Already subscribed? sign in

AngularJS Architecture: Control your promises with $q

6:26 Angular 1.x lesson by

Learn how to manually control how asynchronous requests are handled with the use of promises. Because $http is built to work with promises, we saw a foreshadow of them in the previous lesson. We will take this a step further but seeing how to manually create a promise and then resolve or reject it as we see fit.

Get the Code Now
click to level up

egghead.io comment guidelines

Avatar
egghead.io

Learn how to manually control how asynchronous requests are handled with the use of promises. Because $http is built to work with promises, we saw a foreshadow of them in the previous lesson. We will take this a step further but seeing how to manually create a promise and then resolve or reject it as we see fit.

Avatar
Reinder

It seems that depending on the timing of the getCategoryByName and the getCategories call, additional getCategories HTTP requests can be triggered as there is a (n obvious) delay between calling getcategories and setting the categories in the caching functions after receiving the result. If getcategoriesbyname is triggered in between these events, (categories) will be false and therefore an additional call will be triggered.

What is the best-practice way to resolve this type of situations in angular to avoid multiple triggers? Maybe setting a loading variable and put a watch on this variable so the getcategory by name function can be resolved after the getcategory call is resolved in the promise?

Very good series to get started in Angular!

In reply to egghead.io
Avatar
Lukas

Hi Reinder --
Generally this is a non-issue and depending on the payload, I may even just let it happen as it has no real effect on the behavior of the application. With that said, I have used a flag to essentially bounce additional calls until the original call finishes. So what you said. :D

In reply to Reinder
Avatar
Reinder

Thanks, I understand it is not an issue in this app, i'm using the tutorial to learn angular and build a similar but different app in parallel, there I do have this issue sometimes.

This could also be an issue in my understanding of the databinding and I should copy the full bookmark list instead of returning a sub-array (new object) from the model to trigger this correctly.

In my app I'm using two views on the same data array to display the data (lets say bookmarks and favourite bookmarks) where the bookmarks should be loaded once and the controllers of the views get the right bookmarks based on filtering the list. In this case depending on the timing of the calls the bookmarks can be loaded twice. If the view is refreshed and the array re-loaded the bookmarks do show up.

In reply to Lukas
Avatar
Rick

Hi Reinder,

I ran across this very situation. The equivalent of the getCategories() I had was quite expensive on the server side, so having it go out and do that call multiple times was getting a little too lengthy for me.

What I ended up doing, and keep in mind that I'm still learning this as well, was as follows:

I added a var categoriesPromise along side the categories and model variables, and then in the model.getCategories function, I changed it to

return (categories) ? $q.when(categories) : (categoriesPromise || categoriesPromise = $http.fetch(URLS.FETCH).then(cacheCategories);

In this way, I hold on to the original $http() promise and return that rather than generating a second one. So, both the promise and the result are cached. Seems to work. Again, I'm still new to angularJS and $q, but so far, this seems to working (I could be missing something obvious tho!)

Hope this helps rather than harms. :)

In reply to Reinder
Avatar
Reinder

Hi Rick, thanks for sharing your solution. The solution I have implemented is by setting a loading flag on loading and a watcher that is removed after the update to retrieve the content.

I haven't checked your solution in mine but I sometimes need to reload the full list from the backend for which i use the same function, when keeping the promise as variable this might result in the original promise.

If it works, it works :)

In reply to Rick
Avatar
Weston Ross

Where do you draw the line between making a http request to store all your model data on the browser and making individual calls for each CRUD action ? I understand making the the call for the json files, "caching" the values and using lodash is for demonstration. But I also understand that angular and SPAs have an advantage that they make very few calls to the server. Is the method you have illustrated in this tutorial only for demonstration purposes only?

In the previous lesson, we learned how to make a remote server call using the http service that is part of the AngularJS core. The http service is built on top of promises. It implicitly returns a promise when you make a call to the server using the http service.

There are situations where you may want to actually do something before you actually pass that data back to the controller for consumption. For instance, what if you wanted to actually return a cached version of that data structure, not actually incur the overhead of making an extra call?

You would need a way to manually resolve that promise instead of allowing the http service to do that for you. The goal is to show you how to use promises in your model and do it in such a way that your controllers are oblivious to whether or not it was coming from the http service or you were manually resolving it.

This is where the q service comes in. AngularJS uses the q service to handle promises. We are going to inject the q service into the categories model. From here, let's go to where we're calling getCategories. Let's check to see if we actually have categories. Does it exist? Have we already made this call?

If it does exist, let's go ahead and just return a promise. We can use a method on the q service called when. What this does is it takes an object and it wraps a promise around it.

Now, what we're doing is we're saying, if categories exist, go ahead and return a promise using this when method. In the promise, put the categories. At this point, we are actually going to check to see if categories exist. If it does, then it will be as if we already made a call to the server and got categories.

This is a really gentle introduction into promises and probably the easiest scenario that you're going to see. But let's take this a little deeper. We are going to create a method called getCategoryByName. This is going to be a method that we're going to need in a future lesson. We'll go ahead and create it now.

We are going to create a deferred object by calling q.defer. This returns a deferred object. What I'm going to do here is create the bookmarks or the bookends for this. We actually create a deferred object and then we return the promise.

From here, we can manually resolve or reject the promise at our discretion. It's important to understand these two parts, is that you create a deferred object by calling q.defer. Then, you return the promise object on that deferred object.

From here, let's go ahead and fill this in. I'm going to create a convenience method here called findCategory. What this is going to do is just loop over the categories collection and compare the name of the current category to the categoryName parameter that we actually need to define up here. There we go.

What this is going to do is loop over the categories and find the category with that name. Fairly simple.

From here, we will say, does categories exist? If so, then we are going to resolve the promise with the results of findCategory. The reason why we're checking if categories exist is because you could possibly want to find a category before you've actually had the opportunity to go and fetch the categories from the server.

If categories does not exist, then we need to call getCategories. From there, take the result and we will use the result to resolve the promise like so. We are simply just adding in an extra step.

If categories exist, we're just looping over the categories and finding the category for that name and resolving the promise with that value. If it does not exist, we are making a call to the server, getting the categories, and then resolving the promise that we created with the results of findCategory. We're just inserting that extra step.

This is the technique for manually creating a promise. We return it to the consumer and then we can either resolve or reject it.

Stay tuned for the next lesson where we will continue to refactor the functionality from the main controller back into the appropriate parts of the eggly application. I look forward to seeing you in the next lesson.

HEY, QUICK QUESTION!
Joel's Head
Why are we asking?