Our first real life experiment with AngularJS is a mobile and touch-friendly carousel directive.
As this project shed some lights on various AngularJS aspects, we’d like to share it with you; The full demo+code is available here : http://blog.revolunet.com/angular-carousel
The goal is to transform any
ng-repeat directive into a carousel, just by adding the
rn-carousel attribute to its
ul container. The directive take care of the dirty work for you, without any jQuery.
Here’s a very basic example of a Youtube API call, with the data displayed in a regular Angular template; We just add a
rn-carousel attribute to the
ul element to ask a carousel :)
rn-carousel directive is responsible of :
1) wrapping the real ul/li in a carousel container and add CSS classes
We first wrap the original
ul block in an outer div using JQlite
wraps method during the directive link phase. This cannot be doneduring the compile phase as it would lead to an infinite recursion loop. (AngularJS will wrap again an again as the original directive stays untouched).
Then we add some CSS classes and an unique carousel identifier class so we can target that instance easily later.
The CSS makes the ul/li block appears as a single horizontal line composed of many li blocks, so its very easy to slide the blocks horizontally.
2) watching the ng-repeat expression
Because we want to monitor precisely the items in our carousel, and we want to be able to dynamically add/remove items, we need to extract and watch the original
ng-repeat expression. We can then watch that expression and then be notified when its updated. This is useful to know how many slides we have and update our indicators accordingly for example.
This extraction is done by literally parsing the ng-repeat expression in a generic way, using the generated normalized comment node. Credits goes to @bennadel for this awesome trick.
Update : I ended up with just extracting the li
ng-repeat attribute during the
compile phase which is much less overhead.
3) binding touch and click events
Like most directives, we bind events in the linking phase. We just take care of binding both touch and mouse events.
1 2 3 4
4) animating the slides smoothly
When we detect a
touchstart we just store the initial event position. On
touchemove we update the horizontal offset and move the whole
ul accordingly using CSS. Then on
touchend, we detect the desired position (prev or next) and dynamically add CSS styles and classes to move the slides accordingly, or restore the current position in some cases.
The slide movement is made thanks to the CSS
translate3d properties to force CSS 3D transforms and thus animate the slides using the GPU which make the movement much more smooth especially on mobile devices.
1 2 3 4 5 6 7 8
5) adding an indicator to our carousel
As we already watch the
ng-repeat expression, we track the number of slides in the carousel and so its quite easy to add data-bound indicators to our carousel. You can enable these by adding a
rn-carousel-indicator="true" attribute on your
The indicators are produced by the
rn-carousel-indicators directive, completely decoupled, during the linking phase. For the directive to be executed, we compile it through the
$compile service before appending it to our container.
It just takes
index attributes that will be injected in an isolated scope to produce the indicators and set the CSS classes.
1 2 3 4 5 6 7 8 9 10 11
6) offer a two-way data binding for the active slide index
The carousel current index position can be data-bound to an optional
rn-carousel-index attribute if provided. This allows you to display the carousel position somewhere else, add custom controls/indicators, initialise to a different slide…
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22
updateSlidePosition() function simply moves the
ul block based on the
carouselIndex value and the width of the slides. We do this with CSS transition and
translate3d so this is animated and hardware accelerated.
7) tests and grunt automation
These subjects will have a dedicated article soon :)
- Only works with ul/li structure
- Carousel dimensions is based on the first li detected
UPDATE : the carousel can now be buffered to improve performance with large datasets !
Easy isn’t it ?
If you have questions, suggestions, issues, pulls requests… just comment below or on Github :)