Angular Master Class in Freiburg
Join our upcoming public training in Germany!Get a ticket →
Just recently, we wrote about how to build a zippy component in Angular. We explored how to get started with the framework and learned about some concepts that it comes with to build a very simple component. If you haven’t read the article, you might want to check it out.
As a follow up, we now want to build yet another component that is widely used in a lot of applications: Tabs. Building tabs has always been the de facto example when it comes to explaining directive controllers in Angular. Angular >= 2.x does not have the concept of directive controllers, because the component itself is the execution context. It also makes it much easier to access other directives and components through dependency injection. However, you do want to use directive controllers in Angular 1.x in order to make the migration process to Angular >= 2.x easier.
Want to see things in action first?
Let’s start right away and learn how easy it is to build a tabs component in Angular without the confusing relationship between directive link functions and controllers. We’ll skip the installation part, since that was explored in the other article.
TABLE OF CONTENTS
What it should look like
Before we start implementing the actual component, let’s first clarify what we want to achieve from a consumer point of view. Building tabs with web technologies usually ends up with having a HTML list, that represents the tabs, and container elements per each tab that display the content of a tab.
Of course, in Angular, those implementation details are hidden behind some nice readable and declarative elements that we all know as directives. Having a tool like Angular (and also Web Components) allows us to create custom elements, so that a consumer could use something like the following snippet to add tabs to an application:
We have a
<tab> element that simply represents a single tab which has a title, and we have a
<tabs> element that takes care of making those
<tab> elements actually “tabbable”.
If you’ve been following the development and concepts of Angular, you probably learned that, since Angular 2.x, the consumer of a component is in charge of deciding how a value is passed to a component. Whereas in Angular 1.x, the directive defines how a value is bound to it’s scope, so the consumer needs to know about the inner workings of a directive.
This means, talking about the
tabTitle attribute that we have in the code above, consumers can either write to the component attribute (if it exists), or to the component property. The latter would allow the consumer to pass expressions to the component that first get evaluated. Here’s what it could look like:
Alright, now that we know what we want to build, let’s get our hands dirty with some Angular code.
Building the components
We start off by implementing a rather static version of the
<tabs> element. If you’ve read our article on building a zippy component in Angular,
you know that we need the
@Component() decorator to tell Angular what the selector and template for our component should be.
When using the
Component decorator, we can specify the template using the
template property. The back tick syntax comes with ES2015 and allows us to do multi-line string definition without using concatenation operators like
As you can see, the component template already comes with a static list of tabs. This list will be replaced with a dynamic directive later, for now we keep it like this so we get a better picture of the direction we’re going. We also need a place where the tab contents will go. Let’s add an insertion point to our template. This will project the outer light DOM into the Shadow DOM (Emulation).
Cool, we can already start using our
<tabs> component and write HTML into it like this:
Of course, we do want to use
<tab> elements inside our
<tabs> component, so let’s build that one. It turns out that the
<tab> element is actually quite primitive. It’s basically just a container element that has an insertion point to project light DOM. We shouldn’t forget the configurable
tabTitle. Here’s how we do it.
The element should be named
<tab> so we set the
selector property accordingly. We bind the
tabTitle input to the component’s
tabTitle property. Last but not least we add a template that is just a div with an insertion point.
Wait, that’s it? Well, sort of. There’s a tiny bit more we need to do, but let’s just use our new
<tab> component in our
Executing this in the browser, we notice that we still get a list with
Tab 1 and
Tab 2 and in addition, we see the projected contents of both tabs at the same time. That’s not quite a tabs component, right?
Making the components dynamic
We’re getting there. Let’s first make our
<tabs> component to actually use the correct titles instead of a hard-coded list. In order to generate a dynamic list, we need a collection. We can use our component’s constructor to initialize a collection that holds all tabs like this:
Okay cool, but how do we get our tab titles into that collection? This is where, in Angular 1, directive controllers come in. However, since Angular 2.x it’s much easier. First we need an interface so that the outside world can actually add items to our internal collection. Let’s add a method
addTab(tab: Tab), that takes a
Tab object and does exactly what we need.
The method just simply pushes the given object into our collection and we’re good. Next we update the template so that the list is generated dynamically based on our collection. In Angular 1 we have a
ngRepeat directive that lets us iterate over a collection to repeat DOM. Since Angular 2.x there a a
ngFor directive that pretty much solves the exact same problem. We use the directive to iterate over our tabs collection to generate a dynamic list of tab titles in the component’s template.
If the templating syntax doesn’t make sense to you at all, you might want to check out this design doc.
Alright, we have a collection, we have an API to extend the collection and we have a list in our template that is generated dynamically based on that collection. Since the collection is empty by default, the generated list is empty and no tab title is shown. Somebody needs to call this
That’s where the
<tab> component comes into play (again). Inside the component we can simply ask for a parent
Tabs dependency by using the new, much more powerful dependency injection system and get an instance of it. This allows us to simply use given APIs inside a child component. Let’s take a look at what that looks like.
Wait, what happens here?
tabs: Tabs is just Typescript type annotation which Angular uses for Dependency Injection.
Please check out our article where we go deeper inside Angular DI
Angular Hierarchical Injector knows, that we want first
Tabsinstance that it can get, when traversing upwards from current host. In our case the actual host is our
<tab>component. Injector ask on tab for Tabs, if there is none, Injector will ask Parent Injector for
Tabs. In our case parent Injector is on
<tabs>component and it has indeed
Tabsinstance, so it will return the correct instance of
For now it’s just important to understand that this particular type annotation gives you access to a parent component dependency, which in our case is
Using that instance, we can simply call the
addTab() method with a
Tab object and we are good to go.
Making it tabbable
Using the components as they are right now, we do get a tabs list generated by our tab titles, but we still see all the contents of each tab at the same time. What we want, is to make that component actually “tabbable”, so that only one tab content is shown. How do we achieve that?
Well, first we need a property that activates or deactivates a tab and depending on that value, we either show or hide it. We can simply extend our
<tab> template accordingly like this:
If a tab is not active, we simply hide it. We haven’t specified this property anywhere, which means it’s
undefined which evaluates to
false in that condition. So every tab is deactivated by default. In order to have at least one tab active, we can extend the
addTab() method accordingly. The following code for instance, activates the very first tab that is added to the collection.
Awesome! The only thing that is missing, is to activate other tabs when a user clicks on a tab. In order to make this possible, we just need a function that sets the
activate property when a tab is clicked. Here’s what such a function could look like.
We simply iterate over all tabs we have, deactivate them, and activate the one that is passed to that function. Then we just add this function to the template, so it is executed whenever a user clicks a tab, like this.
Again, if this template syntax is new to you, check out the mentioned design document. What happens here is, whenever a
click event is fired
selectTab() is executed with the iterator tab instance. Try it out!
Here’s the complete source in case you ran into any problems.
And we shouldn’t forget that these components need to be declared on our application module:
Where to go from here
This is a very rudimentary implementation of a tabs component. We can use that as a starting point to make it better over time. For example, we haven’t done anything in terms of accessibility. It would also be nice if the component emits some custom events when a tab is activated. We’ll cover working with events in Angular in another article.
Angular is so awesome that there is not just one way how to do things!
We can take a totally different approach how to implement our simple tabs ( which isn’t so easily possible in Angular 1.x ),
leveraging special Angular
@ContentChildren property decorator with
QueryList type and
AfterContentInit life cycle hook.
Those are more advanced concepts, which are covered in more details by Juri Strumpflohner in his follow-up article.
If you’re just curious what it looks like, check out the demos below!
Get updates on new articles and trainings.
Join over 1400 other developers who get our content first.
Three things you didn't know about the AsyncPipe
This article explains three lesser known features of the AsyncPipe that help us to write better async code.
Using Zones in Angular for better performance
In this article we'll take a look at how to use Zone APIs to improve our app's performance!
Making your Angular apps fast
In this article we discuss tips and tricks to make Angular blazingly fast!
Testing Angular Directives with Custom Matchers
Deliver terse, DRY, self-documenting unit tests with Angular using custom Jasmine Matchers and Helper functions.
Testing Services with Http in Angular
Want to learn how to test services in Angular that have an Http dependency? Read more here!
Two-way Data Binding in Angular
Two-way data binding was one of the main selling points of Angular. Since version 2.x, we can build directives that...