Become a member
to unlock all features

Level Up!

Access all courses & lessons on egghead today and lock-in your price for life.


    CodeSchool Refactor - Flexible Directives Part 2/2

    John LindquistJohn Lindquist

    In collaboration with CodeSchool, John takes the reusable directives from the previous video and works them into more flexible and easy to use directives.



    Become a Member to view code

    You must be a Member to view code

    Access all courses and lessons, track your progress, gain confidence and expertise.

    Become a Member
    and unlock code for this lesson




    Presenter: While we've done a good job at making the product tabs directive something that we can reuse, for example, I can just duplicate it, pass in a different product. You can see we can have two product tabs living side by side or on top of each other and navigating to different products, so it's obviously something we can reuse.

    The problem now comes from...I'll just undo these changes.

    The problem now comes from being able to make new tabs. You can see that we have these magic numbers, three, three, three, three, and that somehow these relate to each other. I click on one and it shows this.

    I should be able to duplicate this and have a new tab show up without it interfering with whatever else is going on, because if I clicked now...If I refresh, it shows two reviews, and they both highlight and it gets weird.

    Let's make this a bit more flexible by using some more angular features. The first thing I'm going to do is set up the directive as I would like it to look, because marking out directives on your HTML before writing any JavaScript for them is a good practice.

    First I'd like to have a tab group which has three tabs in it, and inside each of those tabs I want my Product Description, I want my product specs, so Product Specs, and I want my Product Review.

    I feel I should be able to manage these tabs without putting any logic inside of the HTML. That's simply by clicking on one it would deactivate the other two. I should be able to create new tabs or delete other tabs without having to reorder them or add any logic, to keep this as flexible as possible.

    Let's pass in a product to each of our current directives and start from there. Use the second product here, we'll use the second product here of course, and this one was Reviewed Product. We'll paste that and clean this up.

    If we refresh we should have these sitting side by side. Here's our current working product tabs directive, and beneath it here is our tabbed group. Let's put a HR here to separate the two. Above this horizontal rule is our current product tabs and beneath it is the one we're working on.

    Let's start by creating our tab directive. We're also going to need something else, a title, which will be a description -- because this is what's going to go on the tab or the button that we use -- Specs, and title is Reviews. We can start working on the JavaScript.

    We'll create a product directive called "Tab." Then we will return our directive description object. We want it to be an element, so we'll pass in E. We want the scope to take in the title, so we'll say title is a string. If you use the "@" sign it means we're passing in a string. The equals sign would have been the double-2E binding, but this time we just want the string of the title.

    A simple template, we'll use an inline template instead of a template URL. We'll just say "div," and we want to show the title in here. We'll just give this a class of button and button default. If we refresh now, you can see that we have three buttons -- Description, Specs, and Reviews -- and they are not toggable or anything.

    You've probably noticed that we lost all of our content inside of our tabs, so there's no description or specs or reviews content. That's easy to solve because this content doesn't show up because we used a template here, and this template is overwriting what's already there.

    To take that content out and to put it into our template, we use a concept called transclusion where we type the word transclude and say true. We say we want our transcluded content to be in a div underneath this.

    NgTransclude goes here, and when I refresh you can see that we have description with a content, specs with a content, and reviews with a content. What it did, the transclude yanked the content out of here and then dropped it into this div where we said to use it because we marked transclude as true.

    The really fun part comes from trying to manage which of these buttons is active, and making them toggable and deactivating the other ones. You can see in our previous attempt that what we did is we accessed a controller and said is set and then built that logic into a controller.

    We're going to take a similar approach, except this time the controller is going to be on our tab group directive, which is the parent of our tab directive. We'll have these tabs communicate with the tab group and let the tab group manage these tabs.

    The way that this works is that we will create a tab group directive. I'll say product.directive and tabgroup, and then return our object, which is an element and which has a controller, and then this controller has an API. We can say this addtab, and pass in tabs. A way to track and manage these tabs.

    To quickly see this in action, let's log out the tabs as we add them. In here, to be able to add tabs I will need to use something called a linking function. I'll say link, which takes the scope element attributes and an option controller, and will require that controller by saying look up to find the tab group and then use the controller of tab group as this last parameter.

    When these directives are linked together, take the controller to add a tab which will use the scope, so we can use and watch those scope properties.

    If that last part made no sense whatsoever, please check out the directive to directive communication videos and the link videos and the directive controller videos on egghead.io where I go into more detail on that.

    Right now we need to move forward, so let's see what this did. Once I refresh you can see I got the scope off of each of the tabs, passed into the paired controller from tab group, and I'm able to access them and use them.

    Let's store all of these tabs on the scope of the controller because we're going to loop through them later. Instead of logging them out we'll say scopetabs pushtab, and then we can also say if the scope tab's length is equal to zero, so we're on the first one, we want to set this one as selected, so selected true, and then in our template we'll do that same ngclassActive, if it's selected.

    If I refresh, the first guy should be selected. He's selected, and the others aren't. Now, before working on the toggling actions of each of these, you've probably noticed that these are not side by side, that they're stacked on top of each other.

    Because of the way that we structured our HTML with the content inside of the tabs. Because this is the way we want to describe it, we'll have to do a bit of a hack here inside of our directive where we pull the template of our buttons and the title up into our template for our tab group.

    We'll say template, and that's the same as this. It's that now we're going to ngRepeat and say tab in tabs, because we have each of the tabs here. The tab needs to be tab.active, and the title needs to be tab.title, so that when we refresh here -- and you can see that we've broken everything, it's completely gone, and that comes back to transclusion again.

    If we don't transclude the tabs into the tab group, the tabs never get added and pushed into the tabs array, so there's nothing to ngRepeat through.

    Let's go ahead and transclude true, and then we can add those transcluded tabs, so div and ngTransclude, and now when we refresh again you can see we have our tabs back and all three pieces of the content.

    Let's make it so that we can toggle through these tabs and hide and show the proper content based on which one's toggled.

    We'll say ngClick, and we'll say select, and we'll pass in the current tab of the ngRepeat. This will be the tab or the scope that's passed in when this is added. We'll say scopeselect=functiontab, and we'll loop through all of the tabs using the angular for each scope tab, so this will loop through each of the tabs we have. For each of those tabs that are passed in, we'll call this each tab, because these are the tabs in the four each.

    We need to check if the tab that was clicked is the same as one of the tabs in our array, so we can say eachtab.selected is assigned to whether or not the angular equals, so this can check two objects, tab and each tab.

    This means that if this is not the same tab that you clicked on, that we want selected to be false. But if it is the tab that you clicked on then we want it to be true.

    One quick fix I noticed here is that I said tab.active. That's the class name. I want active class to be active. It's if the tab is .selected, like we went over. If I refresh now, you can see I can click on these buttons and they will toggle and stay toggled and disable the other one that you just clicked on.

    The last thing we need to do is to only show the content that is selected. If Specs is selected, we only want to show Specs. That's as simple as going down into our tab, saying ngShow if it's selected, because this selected is the same as this selected because this scope is passed into the addtab and tab.selected here, again, or tab.selected here is the same as this selected.

    Once we do that and we refresh, you can see Description, Specs, and Reviews. We have the same component as we have up above, albeit styled a tiny bit differently.

    While we have a bit more code here in our two directives and our tab and in our tab group, when you look into our index HTML you can see that we have our tab group with some tabs and the necessary components in it.

    For example, if I want to add another description at the end, I can copy, paste, refresh, and you can see Description, Specs, Reviews, Description. I can easily change the order of these around. I'll cut those and paste them beneath and refresh, and you can see Description, Description, Specs, Reviews.

    If you contrast that to what's going on in here, with what we originally started with, you can see how much more difficult it would be to do tab is set and reworking all the numbers as you move things around or add new things.

    To quickly recap what we did, because I know we went through a lot.

    We created a tab group directive and a tab directive. The tab directive can take content inside of it, and that content in our tab directive will be transcluded down into this current div.

    Whenever we add a new tab, our tab directive is going to communicate with the tab group directive by this being the fourth parameter and telling it to add a new tab.

    Whenever a new tab gets added, it pushes it into a tabs array, which our tab group can manage. Then in that array, it loops through and creates the tabs for us in our template. Basically saying, if I click on one called select, use the classes I want, only make the class active if the tab is selected, and use the title that we passed in.

    The selected logic is pretty simple. It loops through each of the tabs. It takes the tab, compares it with the tab that was clicked. If that tab was clicked, selected should be true. If it's not true, selected should be false. Then based on whether selected is true or false, make sure to show or hide the content underneath the tab.

    For our last trick, to show off how flexible this is, we can take the entire tab group, copy it, put it down as the content of reviews. We'll call this Reviews tab group, we'll refresh over here, and you can see Description, Description, Specs, Tab Group. Now we have a tab group underneath our tab Description, Description, Specs, and Reviews.

    We have tabs nested inside of a tab group nested inside of a tab nested inside of a tab group.