AngularJS Dynamic Bootstrap Carousel
Recently, I have been playing around a lot with AngularJS. I discovered it at my current job and decided to transfer my website, https://tinycountrygames.com, to Angular.
If you don't know what Angular is, it is a JavaScript web framework used to make Single Page Applications using an MVC model. That may seem like a lot of techno jargon, but it doesn't mean anything too fancy. A Single Page Application is basically a website or web application where the browser only really reads one file, and the application determines what to display based on the url of the request. So in traditional web development, you would create folders and files, and your urls will be references to those files directly, and the server serves (no pun intended) those files to the web client. Angular does a really good job with the SPA model especially with one of my favorite Angular feautre, ngRoute
, which maps the urls to different template html files on the site. As for what MVC means, it stands for "Model View Controller". It's an application design model that is very popular to use for the web. What it means is that there are three layers to the application. The model, which is the data layer, holds all the data that is displayed on the view. The view, which would be the front end, is how the data is displayed. The view is updated based on the model, but the model is not affected by the view. The controller is what links the view to the model. The controller is in charge of taking in input and updating the model. In angular's case, the controller is also where data from external sources, such as a database, is loaded into the model. I will link below to the Wikipedia pages for an SPA'a and the MVC model:
https://en.wikipedia.org/wiki/Single-page_application
https://en.wikipedia.org/wiki/Model%E2%80%93view%E2%80%93controller
So what does this have anything to do with bootstrap carousels? Don't worry, I will get to that soon enough. But first, I need to talk about one of Angular's most useful features, which is the ng-repeat
directive. This directive takes a list of data from the model and creates an html element for every element in that data. Think of it as a for each loop in regular programming. Let's look at an example. Say you had an application with the following array in the model:
people: [
{firstName: "John", lastName: "Smith", age:30},
{firstName: "Marry", lastName: "Sue", age:28},
{firstName: "Jeffery", lastName: "Johnson", age:40}
]
You can easily display this in the view in a table by using the following HTML:
<table>
<tr>
<td>First Name</td>
<td>Last Name</td>
<td>Age</td>
</tr>
<tr ng-repeat="person in people"> <td>{{person.firstName}}</td> <td>{{person.lastName}}</td>
<td>{{person.age}}</td>
</tr>
</table>
Note that the syntax of the directive is similar to a for each loop in most programming languages. So basically it's looping through each object in the people array, and assigning each individual element to the person variable, and using that in the loop. In angular, the {{...}}
are used to reference data from the model in the view. So the code above would produce the following table:
First Name | Last Name | Age |
John | Smith | 30 |
Marry | Sue | 28 |
Jeffery | Johnson | 40 |
Cool right? Well the power of this directive doesn't end there. You can do all sorts of neat data driven stuff on your site using this directive. For example, you can define your website's navigation based on the model and use the directive to update the website based on the model. You have the ability to hard code these values, or even have them hosted on a database. The sky is the limit when it comes to making data driven sites using AngularJS.
So let's finally get into how to make dynamic carousels. First, look into how to make carousels in the first place using bootstrap. w3schools has a great article on this here.
So you can probably already see how to make these dynamic carousels using the ng-repeat
directive. Before moving on, you need to learn how to set up an angular application. You can look up how to online, there are some great tutorials and articles out there (I may write one myself at some point).
Let's start making a carousel already! To get started, your model is going to need a list of images to display. You can decide on a structure, and add whatever data you want, but for the sake of this, I am going to keep it simple. The model is going to have an array of objects with the following structure:
$scope.carouselImages = [
{src="...", alt="..."}, {src="...", alt="..."}, {src="...", alt="..."}, {src="...", alt="..."},
...
]
I am not including actual data in this article because that is irrelevant. Where you get your data is up to you, it can be hard coded, or from a database. So let's jump into the view and load the data into a carousel.
First, let's build the skeleton of the carousel.
<div id="dynamicSlider" class="carousel slide">
<div class="carousel-inner">
</div>
<ol class="carousel-indicators">
</ol>
<a class="left carousel-control" href="#dynamicSlider" data-slide="prev">
<span class="glyphicon glyphicon-chevron-left"></span> <span class="sr-only">Previous</span> </a> <a class="right carousel-control" href="#dynamicSlider" data-slide="next">
<span class="glyphicon glyphicon-chevron-right"></span> <span class="sr-only">Next</span> </a>
</div>
Let's get to the good stuff right way, loading our images into the carousel. Now is where ng-repeat
will shine. In the div
with the class carousel-inner
, add the following code, naming everything according to your model:
<div ng-repeat="image in carouselImages " class="item" ng-class="{active:!$index}">
<img ng-src="{{image.src}}" alt="{{image.alt}}" class="img-responsive">
</div>
Now let's break all this down. I already showed the structure to ng-repeat
, so let's talk about the class attributes. Every iteration of the loop will be a div
with the class item
. This is per the structure of a bootstrap carousel. However, exactly one element must always have the active
class, which is how bootstrap and the browser knows which image is currently displayed. Generally it makes sense to use the first image in the list as the first active one. To have the class of an element be dictated by Angular, use the ng-class
directive. This directive can work in many different ways, and can take all sorts of different inputs. One thing to keep in mind is all classes declared in the directive are added to the classes declared in the class
attribute, so the two can work symbiotically, as they do in this particular example. In this example, the syntax for the directive is ng-class="{cssclass:expression}"
. So if the expression evaluates to true
, the element will be created with the class. As for the expression itself, one neat thing about ng-repeat
is that you can retrieve the index of the current element using the $index
. So the way we determine whether an element is the first element or not is by exploiting a bit in JavaScript where the number 0 always evaluates to false
, and all other numbers evaluate to true
. Because of this, the first element of the list, with index 0, will evaluate to false
, and the other elements' index will evaluate to true
. So we need to negate this using the !
operator, which will make the first element return true
. Next is the image element. The only thing of note is the use of ng-src
instead of regular src
. This has to do with how the image is evaluated in the browser, and using the Angular directive prevents any errors, check out this stack overflow question for more details.
So now, our images are being loaded dynamically. However, we have no indicators. We're going to use very similar code to loading in the images, but I am going to feature another neat thing about ng-repeat
. So in the ol
element, add the following code:
<li data-target="#dynamicSlider" ng-repeat="image in carouselImages" ng-class="{active : $first}" data-slide-to="{{$index}}"></li>
Can you see what that neat feature is? Here, instead of using the $index
to determine whether or not the element is the first, I am using $first
, which is a built in boolean
that already tells whether or not the current element is the first in the list. There's a whole list of special properties that are available when using the repeat directive.
So you may be thinking to yourself that we're done here. The images are loading in and the indicators are as well. Well, obviously, there would be no need for this post if it were this simple. Go ahead and try to use the navigation on the left and right of the carousel. You might encounter an error. You might see that the links actually navigated in your browser and you might see an Uncaught TypeError: Cannot read property 'offsetWidth' of undefined
in the console window. This is because, although I have been singing it's praises, Angular isn't perfect when other libraries, like the bootstrap carousel, are trying to access dom elements. But have no fear! The work around for this particular issue is quite simple and only needs 1 very simple JavaScript function, and the use of 2 new directives. So the first problem might come from the fact that the links are loaded before there is any item with the active
class in the carousel, causing the bootstrap engine to get confused. To mitigate this, we will use the ng-if
directive. The ng-if
directive literally adds and removes the physical dom of the element based on the expression passed. Don't confuse this with showing and hiding, as hidden elements are still visible in the element view of a page. Using ng-if
will prevent elements that are not supposed to be on the page from being seen anywhere, and will bring them back only if the expression passed is true. So first, remove the data-slide
attribute all together from each <a>
tag because we are not going to need it once the fix is implemented. Next, add the following directive to those <a>
tags:
ng-if="carouselImages.length > 0"
So now those links will only appear on the page if there are images in the list. Now, we need to add a function to our model. Luckily, bootstrap allows for carousel's to be manually controller in javascript. We simply need to make a function to be run when those links are clicked that navigates the carousel. One thing to note is that any functions that the view calls must be made in the model of that view. So declare the following function in the model like below:
$scope.carouselSlide = function (selector, dir) { $(selector).carousel(dir); }
This is making use of jQuery, I know it could be easy to get your $
's all confused. So basically this function calls the carousel
function in bootstrap on the given element and navigates it in the given direction. It's made to be flexible and is only really a way to pass those parameters onto the real function in bootstrap. But now that we have it, we can implement the work around in our view. To do this, we are going to add an ng-click
directive to our links. Basically you just pass a function call into the directive, and that is called when the element is clicked. But note, you can only use functions defined in the model of the view. Note that when there is a ng-click
on a link, the default behavior of navigation will be prevented. So, on your indicator for the left, add the following directive:
ng-click="carouselSlide('#dynamicSlider', 'prev')
And on the right indicator, add this directive:
ng-click="carouselSlide('#dynamicSlider', 'next')
And that should do it! Leave any questions you have below, and I hope you found this to be helpful!