A Quick Guide to Implement a Cross-Browser AngularJS Pull-to-refresh Directive

What made me do this?

As a newbie in frontend development, I have to admit, a few months ago I couldn’t even imagine myself writing my own UI component. In those good days, I just carelessly included other, ready-to-use components and never thought about why and how they really work. Sometimes these plugins caused me some headaches but I always managed to avoid going down to the lower pits of their implementation. Needless to say, this cloudless state didn’t really last too long.

One day a quite reasonable and seemingly simple request arrived in our office. One of our customers raised the question whether it is possible to have a pull to refresh feature in his web app. Our answer was quick, self-confident and achingly unconsidered contemplating at this distance of time. „Yes, it’s easily attainable”- we answered, after ascertaining that there are plenty of implemented open-source pull to refresh directives available all over the Internet.

I assume, the end of the story is quite predictable, considering the title of this blog-post. Soon I ended up testing dozens of pull-to-refresh directives, one after another, always running into some unforeseen bugs. Either they did not work with all the required browsers, or they were not able to handle content with dynamic sizes. After all of these efforts I came to a desperate but ambitious decision. I decided to write my own pull-to-refresh directive, combining the existing ones I found and tested before.

angularJS pull-to-refresh

The most important criteria

According to my previous experiences with the components I tried, I collected the most important requirements that can be imposed upon a directive of this type:

  • should be able to handle content with dynamic sizes (like Angular dynamic sized lists)
  • should work perfectly on common browsers (tested in: Chrome, Mozilla, Safari and Opera)
  • should work properly on every device, regardless of the display size
  • preferably should have a highly customizable UI

During implementation, this list helped me a lot. While testing, it always reminded me of the most critical user cases. I had to solve plenty of tricky bugs to meet these requirements, and in the next paragraphs, I will share you some tips about how I solved these.

The basic architecture

Basically the task is simple. First of all, we need to be able to register the state of the scroll-able element. Is it scrolled to the top, or even “overscrolled”? I decided to achieve this by using element.scrollTop attibute. It is very important that it works only with a fixed size container, that doesn’t grow with the growth of it’s content. To secure responsibility, I preferred using a fixed position container instead of setting the exact height (pulltorefresh-frame).My implementation looks like this:

  • tip1: use ng-transcude! this way, the template that originally was written between <pull-to-refresh> tags will be rendered between <ng-transcule> tags inside the view of the pull to refresh directive.
  • tip2: for Safari, it is useful to set “-webkit-overflow-scrolling: touch;” attribute, to preserve momentum scrolling
  • tip3: to achieve much better usability, it is good to make the position of the pull to refresh frame configurable (see later)

The directive should check all the scroll events happening on the list. To achieve this, the best tools are javascript event handlers, such as touchstart, touchmove, touchend. It is highly recommended to  use the link option to register DOM listeners as well as to update the view. It is executed after the template has been cloned and is where directive logic will be put.

The directive has an updateFn parameter as an input. This is the parent controller’s function, which is called when a refresh is needed. It also has a refreshInProgress flag, so that animations and other view manipulations can be bound to the value of this variable.

Summarizing these, the directive should look like the following:

The heart of the directive

I think it is very useful to summarize the exact tasks of these event handlers.

angularJS pull-to-refresh flow chart

  • On “touchstart” the only thing we need to do is to register where the user started to scroll.

  • On “touchmove” we need to check where the user moved his/her finger from the touchstart point. This can be managed by comparing the current position to the original one.  Also we need to check whether the list is scrolled to the top (allowUp is false). If the direction of the scrolling attempt was upwards and the top of the list is reached, the user can start to pull, and a reachedTheTop flag needs to be set. To be able to check whether the pull attempt was “strong” enough, the current position of the touch event needs to be saved, as the starting position of the pull attempt.
  • It is also very important that the user shall get feedback about the state of the  pull attempt. This can be managed by dynamically changing the css styles of the elements (progressBarUpdate function – see later).

  • On “touchend”  if the reachedTheTop flag is set, we need to check the strength of the pull attempt. This can happen by comparing the beginning and the end position of the pull action.
  • The user should get a feedback about the result of the pull attempt (for example: refresh icon circulating)
  • The state of the loading bar should be set to the default state, regardless the result of the pull attempt

Finally! We successfully achieved to handle pull attempts! We all can take a breath after surviving all the terrible ordeals while fighting against strange mobile browser behaviors and terrible challenges. Great! But if you have ever dealt with frontend development, you should already know that a very important issue has remained uncovered…

Mirror, mirror, on the wall, what the hell happened?!

Please note, that web development is a unique island in the  endless seas of IT sectors where beauty and design DOES matter. In case of UI components, it is extremely important to give proper, easy-to-understand feedback. Sprinkling this with a pinch of design, our brand new pull-to-refresh component becomes a useful tool of mobile websites. Though I don’t want to constitute any limit on the creativity of the web designer living in you, here are some tips and examples showing you how to manage view manipulation.

In my example project, I chose a pullable loading bar, inspired by the native Android solution:

angularjs pull-to-refresh before actionangularJS pull-to-refresh in action loadingangularjs pull-to-refresh in action

To add the loading-bar to the directive, you need to extend the directive’s template, for example like this:

The view has to be manipulated according the state of the pull attempt. With the help of the event handlers, we can calculate and update the values defining the state of the progress bar. All the magic happens in the mysterious progressBarUpdate() function, that has been mentioned before. The propreggBarResore function also manipulates the view, when the pull attempt is over:

There are plenty of possibilities to manipulate view, and as it is a quick review, let me introduce you the simplest one. This function changes the opacity of the progress bar depending on the parameters of the touch move:

Final steps – the more configurable the better

The big questions of life usually force us to make tough decisions. What if I have a well-designed, pretty website with all the beautiful shades of yellow, and I find a pull to refresh directive blazing in blue?  Things get quite difficult at this point, because it’s common sense that these two colors simply hate each other. On the other hand it is quite wasteful to omit a well-functioning directive for such silly reasons. To rescue people from the painful hours of diving in the pits of the implementation, and trying to override the content of css and javascript files, it’s very useful to make as many things configurable as possible.

In this case I decided to make the color of the progress bar, the required pull strength and the header height configurable. This way, the directive has three additional inputs. We also have to put a configuration function in the link function where the default values of the inputs can be set, and the effect of the inputs can prevail. All in all, following some little changes, the implementation will take it’s final shape:

Summary

Finally… blast off!  Our brand new pull to refresh component is ready to be released into the endless fields of the World Wide Web. I hope you found it useful, and feel free to add any observations. I myself had a very good time accomplishing this little challenge, and continue to get a large portion of self-confidence when facing problems like this.

angularJS pull-to-refresh DONE

You can find the whole project on https://github.com/scsenge/pull-to-refresh (GitHub) or check it out in action on https://plnkr.co/edit/czMKiz (Plunkr).

Csenge Sóti

Csenge Sóti

Web-Developer at Wanari Ltd.
“Life is a series of building, testing, changing and iterating.” -Lauren Mosenthal