Lesson Description
The "Tab Groups" Lesson is part of the full, Advanced Angular: Performance & Enterprise State course featured in this preview video. Here's what you'd learn in this lesson:
Alex creates a tab group and provides a locally scoped tab state using Angular’s DI hierarchy. He shows how to read projected tabs with contentChildren, activate them via shared state, and initialize a default active tab using signals or lifecycle hooks.
Transcript from the "Tab Groups" Lesson
[00:00:00]
>> Alex Okrushko: Let's look at our tab group. Tab group. And then this is the component that has a selector of the app tab group. By the way, we keep prefixing with app. Why we do this? Well, we have a rule here that we have a prefix of app. If you have your own prefix for your app, you can do it here. It kind of will enforce it. So we have the tab group, which is app tab group.
[00:00:45]
We'll have some template as well, but this is what's important here. We can also have a list of providers here. We have imports, but we can also have a list of providers here. We typically, so far we provided everything either directly in our app config which our general global provider list, but we can. With at state injectable, we didn't provide it in root.
[00:01:18]
We're going to provide it in this specific component only. So it's going to be tab state. What's the advantage of this? Why is it not globally provided, does anybody know? So what it means is that this service, this injectable, would be created only when this component is created, and it's tied to its life cycle. And it's tied to life cycle and to its instance to this specific instance as well.
[00:01:55]
So that means if we have multiple tab groups, each will have their own tab state that's tracking its own inner tabs, which we're going to see how we can track that. And that means that also when this component is destroyed, so we basically move away from the page, this service is destroyed as well. So any state that it had, poof, gone.
[00:02:26]
This is super powerful. And notice that we provide it at the tab group level, but then we can also inject it at the tab level. So each tab will use the hierarchy injectors and then go up the tree until it finds the tab state where it's provided. So this is again very powerful, and we're going to see this in our Angular dev tools, our injector hierarchy tree.
[00:02:59]
So this is again very, very powerful. That means you can have a parent component that creates, I call it the branch state. So it's not the global state, not globally available through entire application. It is a branch state, so it's provided in that parent node and available for all of its children. So if tab has another component, that component can technically also inject that.
[00:03:30]
Very powerful stuff, and this is more advanced. This is probably like the final levels of the topics that we're going to touch on, but we're going to be heavily using this in regular applications. All right, so now we have this tab. Okay, so what do we do? I'm going to copy things again, just to speed things up a bit because it's standard.
[00:04:02]
I'm going to go over it though in detail. So we have the template. Let me just right away export the class of tab group. All right, so we have the template. Let's see, template, yes. So we're missing the comma here. Now the tab is, so we're going to go over our tabs. And how do we get tabs? So we're going to see it in a second.
[00:04:44]
But this is a tab group, right? This is a container for tabs. And our tabs are all going to go inside the ng-content. So our tab group is the parent component. Tabs will be within the tab group, and they're going to be in its content. So that means and where we're going to be composing these elements, we'll have the tab group and then multiple individual tabs.
[00:05:09]
We don't need a slot for it because it's just like they're all going to go in this specific content projection here. And moreover, we're going to, the currently active tab will be in the contents because the rest of them will have this like the ifs, and they're not active. They're not going to be displayed. So when we have a button here, which is basically our tab buttons, right, and then when we click on that button, we'll activate that tab.
[00:05:44]
We also have certain things, specific classes to differentiate when this one is, when this tab is specifically active, right? So we'll have some borders with some texts and things like that. Now let's go into the, how do we get those tabs? That's very exciting part. So first of all, we'll just provide that state here, right. So we'll just create the readonly injection here, readonly, readonly.
[00:06:24]
The tab state I'm providing here, I need to inject that as well, state. And I'm going to be tab state. So now I can use it within my components, which we do, right, we do. Now let's take care of this tabs because it's really interesting how we get them. So you might have been familiar or not from Angular, we have concept of view children and content children.
[00:07:01]
So Angular also created new things for those. So we no longer need to have those decorators. We can now use the content children or view children macros as well, similar like we have inputs, outputs. And this is exactly how we're going to get these tabs. We'll say, hey, look for the and the content children, content children. Look in them for elements that are tabs.
[00:07:40]
And this will get us our tabs. Again, see how it does? Tab now is signal. Oh wow, of tabs. We can again, if we have it in the view, what's the view? View, it's in the media template. For example, we can have, I don't know, readonly buttons or something like that, right. So we can do the view children and look for buttons. We need a locator, which is like, yeah, like a button, right.
[00:08:11]
So that's basically we'll have our buttons in the view of view. It means the media template. Content children is something within the content, which is ng-content, something that's projected inside. So none of it is used directly in the tab group. We're going to compose it where we consume the tab group and tabs, but we can access what we're going to be consuming in the tab group already.
[00:08:46]
Okay, I'll highlight this moment one more time when we're going to construct the tabs. But basically now we can read the tabs, get all the tabs from the tab group. We can iterate over them and we can activate the specific one. So let's do that. So first we're going to do write the activate. Activate active. Wait, well, it can actually just do here state because the state is exposed already.
[00:09:29]
We can do tabs, activate, activate tab. But something that I was mentioning yesterday, active, do we have active? Active, yes, active. Active, right, so that's how I can do it. That works too. You can have the method for this. Since we're already using state directly here anyway to active tabs, then we can kind of reuse that as well.
[00:09:58]
So we're using this service almost as like a view model kind of, getting some of the active tabs out of it and activate them as well. That's fine, or you can create a function method as well within the class. All right, so now we have our tabs, our state. We also want to do is by default select the first one if none of them are active.
[00:10:31]
And there's a few ways we can do this. The one that we have in the instructions, I'm going to implement that first. So we can have, we can do the effect. So we're using that effect, the new effect from Angular, and this effect will start tracking any changes in our signals and rerun it every time. But we don't want to rerun it every time.
[00:11:03]
So first of all, let's just get all the tabs, which will be our const tabs. And we're going to get this tabs, right? And so we're going to be running it every time the tabs change. So if all tabs are there, right, so if the length is bigger than zero and our state does not have an active tab yet, active tab, so if we don't have it yet, then we're going to do, and we'll just say, hey, this state, active or activate, depending what you've written there, all tabs.
[00:11:46]
We're going to extract the first one and activate that, right. So we're going to provide this label from the first tab. That's one way to do it, right, through effect. It will basically will run once because after the first time we'll already have the active tab, so it won't no longer need to run. This is using the Angular effects to do this.
[00:12:22]
We can do it differently. We can do it as well, instead of the effect, we can do it after, after next render. We can do the same thing with the after next render, or we can do one more way. And again, it's up to you. It's a very old, let's say old original, original life cycle hook of the components. It's called ng, after content init.
[00:13:04]
So ng after content init is one of the originals, the OG of functions. So after content init, ng after content init, it will invoke when the content is initialized. So we can use that and also say, hey, do basically the same thing. Or you can actually, you can just directly do this. So all tabs, it doesn't know what it is. It'll be basically tabs, right.
[00:13:39]
State activate tabs, the tabs, of course. So we initialize the content, and then we'll just say, hey, you know what, activate the first one. All of them will work. Effect runs a little bit later, but still before your user can do anything. It schedules, effects schedules, it's an asynchronous schedule, sometime later. Angular does not guarantee when exactly.
[00:14:09]
That's part of the effect, but usually it's like the next micro task or micro task. So it runs it almost immediately, right? This just runs as part of the life cycle hooks. They are still there. There are some suggestions that the life cycle hooks might become like less useful. Ng OnInit and others are already less used. I can do a lot of things with signals directly.
[00:14:42]
And then the other ones like ng after content init or there's another one, after, after a content change, which is every time content changes all rerun. View after view init, there's all those original life cycle hooks that some folks are trying to replace with after next render. Those are the new hooks, and those new hooks run just a little bit later, but it's basically all of them unified in one.
[00:15:11]
You have to have it in a constructor and it's also SSR friendly. This should also be SSR friendly because we're just setting some state inside. But yeah, after next render would be universally useful. For me, this one's fine. I don't think Angular plans to deprecate these hooks. I'm going to see how it's working as well.
Learn Straight from the Experts Who Shape the Modern Web
- 250+In-depth Courses
- Industry Leading Experts
- 24Learning Paths
- Live Interactive Workshops