Advanced Angular: Performance & Enterprise State

Add Tabs to Event Details

Alex Okrushko
NgRx, Angular GDE
Advanced Angular: Performance & Enterprise State

Lesson Description

The "Add Tabs to Event Details" 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 shows how to use a tab group on an event details page, organizing content into overview, venue, and speaker tabs while managing tab state and reusability.

Preview

Transcript from the "Add Tabs to Event Details" Lesson

[00:00:00]
>> Alex Okrushko: All right, so now let's get to using this tab group and the tab itself. So we have our event details page, right? This is, again, so this page, we're going to reorganize things a little bit inside of it. We have our header, okay, so this is the event resource loading. This is once it's loaded. This is our content, so this is the last content that we'll be working on.

[00:00:35]
And right now, this is our content. It's a full day location, so we're going to keep that. Location, we're going to keep location. And we're going to introduce our tab groups here. So if we have this app tab group, right now, whatever we put in the tab group, let's see what we're going to put in the tab group. Tab, tab, tab, tab, tab, tab, there you go. Let's just close this as well right away, so whatever we put now inside of it is the energy content of the tab group, so we're projecting it there.

[00:01:23]
It'll be basically added here, right? So let's do that. So what are we putting in the tab group? And we're putting a bunch of app tabs. A tab. And we also, we're required to provide the input for this, so we can say, hey, the first of the label input, so we're going to say the first one is like the overview. This is static, not iterating over something. You can have a config, by the way, if you want to, but this one will just have each tab will be slightly unique.

[00:02:07]
So there's no reason to iterate over them, so this is the app tab label. And again, we need to import this. Let's see, tab and tab group are imported, and they are added to our imports here as well. All right, so where were we here? We were, so tab group and tab, we have the overview tab. And we're going to, what we're going to have this description here. It's basically as is this description.

[00:02:44]
We're going to be pushing directly to this tab. Cool, so we now have the tab with the description. And we close that tab. We'll create a new tab. So we'll have three different tabs. So this one will have the label for this is venue. And this is where we currently have our venue map. We're going to be pushing that here. Let's see, so we do push location there too.

[00:03:20]
Event location is twice, yes, twice, so we're going to push the location there. And we're going to push our defer on viewport stuff. So that's also really cool because this tab won't even render anything until we actually open that tab, so this is the lazy loading content of the tab itself, not also lazy loading, but it's also hydrating on the viewport as well.

[00:03:49]
So let's do that, so I'm going to put the location in my venue, right? That's the typical one. And then we'll have our defer. And there was just some placeholder here as well. So we're going to push this to our tab group now. Defer with the defer. Make sure that nothing is significantly less. I can just have a little different placeholder for this, so it's there.

[00:04:25]
Okay, good, good, good. Just have a little smaller here loading. This div is a little bit smaller, just do that. Okay. Just my bloating. We're going to use that component we created before in here. And we're going to add another final tab. This one's a little bit more involved. Let me just copy over this. Because until now we weren't even displaying the speakers, now we'll have the dedicated tab for the speakers.

[00:04:59]
I'm going to be copy pasting and this is the tab for speakers. We already had the speakers before, if it's larger, then we're going to go over the speaker. I'm going to track speaker each by its label, have some custom nice rounded elements and things like that. If it's not there, we can just say speaker list is coming soon. So, and that completes our tab group, so our tab group looks at the content that's projected in this, extracts every tab and manages its state.

[00:05:40]
Every tab group itself will have a provider, unique provider for tab state, where it would kind of know which tab is active. All right, let's see if it's compiling, compiling, that's great, it's always a good sign. Let's see, so if I reload the page. We can see that the overview is selected as a default tab, right? This is our after ng content init. I can actually remove this venue details below because it's no longer relative.

[00:06:30]
There you go. That one's gone. That one's gone. Perfect. Let's see, do we have this? Don't need this anymore. All right. Perfect, now we have this. Venue is hydrated. When you open it, before that, it's not even there, so we click it, boom, it's there now, it's loaded, has the location, it has the image. Speakers, now we have those nice way to display speakers.

[00:07:00]
And yes. In your particular example here, I can't remember, but you're not separating those three tabs by component, are you? Like, if you needed to have overview as its own component, venue and speakers as their own component, would that blow this up or would that be totally fine? Yeah, so what I'm going to do is actually I have one component that's separate.

[00:07:25]
My venue map is separate. I mean, I could push the whole other thing here as a separate component. That's completely fine as long as it's wrapped with the app tab because then the app tab group can find it and then whatever is in that app tab is what is going to be displaying, right? So we have content within content. Would you be able to have communication between those child components and the parent tab component, like so that you, if you want to keep track of anything?

[00:08:02]
Yeah, so our tab group is 100% reusable, so it's not really communicating to anything that's projected inside. It's only communicating with the app tab, and they kind of communicate with each other to set the active. If you want to, that's actually a good question, let's see the injection hierarchy, how they can actually communicate with each other. I'm just bringing it up because in the past we've had apps where we've had to propagate data across tabs and it's been a disaster performance wise, but using that child, children, yeah, content children, yeah, that you, I can't even wrap my head around the amount of code that saves.

[00:08:45]
Yeah, so you don't really need to use content children unless you want to interact with them directly, so what we have in this content children, by the way, let's see our tab group. Our content children, we are reading the label from that content child. And so how do we get this label? Well, our tab has the read only label that it's exposing. So that's the only way that the group can read this label.

[00:09:22]
If I make it private, you won't see this label, right? So, the only reason we have content children here is so I can select all the tabs, and once I go over these tabs, I can operate on that component directly, right? I can read the label, I can actually call the activate if I really want to, because I don't have to activate here, and this state is private, so I can read this active from that if I need to, so that's the only reason I have this content children so I can basically do that and extract the label from that.

[00:10:07]
See, like the tab has no idea which tabs we have, but once we have them, you can access each one and read whatever the data is. Now we can still communicate with the event details component and anything that we have on the tab we can communicate. First of all, like, even our speakers, right? What we're projecting, we're projecting this from event details, neither tab or tab group have any idea what's inside, and they don't care.

[00:10:41]
They're just like, hey, this is the ng content slot for you, put whatever you want in it. You can put defers inside if you really want to. That will all work. Now, if you have this as a separate component, that's also no problem, right? You can provide inputs to it or it will still follow the injector hierarchy. And we can access anything from the parent. Let's see that, like injector tree here.

[00:11:21]
So our injector hierarchy. We have standalone event. Three providers now this common module ref, okay, that's all great, but element hierarchy, that's what I want to show you. So we can see that our event details, whatever I have provided or available within event details, my tab group has access to, my tabs have access to as well, right? So it's all the way through.

[00:11:57]
Moreover, you can see one thing here is called tab group has one provider. What is that provider? It's a tab state, yeah. That's the tab state, so this tab group now has a provider of tab state, which all the tabs have access to because they're down the line. So if you were, if you had a form where you wanted to either save data on any particular tab at one time, like, let's say you have one save button outside of all tabs, and each of those tabs had form data in them, you know, it could collectively save all those fields inside of every tab at one time to the API.

[00:12:43]
Yeah, if you have your form that's wrapping all the tab groups, you can 100% pass all the field elements. Because they're basically content projected, and the tab and tab group, they don't even care. And in some cases, you know, you'll have apps where they want you to go from left to right before you proceed, so you can only save the first tab before you walk to the next tab, hit save, you know what I mean, like kind of a stepper.

[00:13:16]
Yeah, so that is also possible. It would be a design decision if you want to have a single form or multiple forms within each tab, because then you can aggregate that data in a larger form signal if you want to, right? Because at the end of the day, form itself is nothing more than controller of that schema and tying it to the signal that's passed to it, so it's setting the data for it, and if you remember from the Angular intermediate, when we were submitting the form altogether, we weren't submitting it from the form data, but we were just reading the data directly from the signal, because it's the source of truth for us.

[00:14:08]
The dots, the three dots is, yeah, we were destructuring this as well, yes. Awesome, yeah, but that's really cool, right? So we have this visualization now where we have a provider and what kind of provider that is really cool. Oh, by the way, we also have the profiler here. I didn't show it before, oops, that's, I don't want to save it now, but you can run the profiler, interact with the app, and we can see what things are happening within the profiler as well.

[00:14:45]
Right now, not a lot is happening because we completely removed the zone, right? So really, when we click the app tab, I think flame will be better. We can see what exactly is happening and what exactly is causing, see like it says only show change that and here it goes, right? So, yeah, that's another thing you can do here, profile the application and she is differ.

[00:15:19]
Oh, by the way, one more cool thing here in the components. You can see defer chunks as well. You can see that it's a placeholder right now and when it will hydrate as well. That's really cool, so, for example, we can do the venue here, and now the venue. Boom, we have this app tab defer venue map and we have this image. I'm going to see you. All the insides of it as well.

[00:15:49]
So, and we can see that this defer blocks, I think this was added in Angular 21. Oh, I mean, not Angular 21, Angular 21, yeah, made it possible. I remember we talked about it that the Angular dev tools is not versioned, right? It just works with whatever the latest is. But each version can expose more things to it, so now we see the deferred blocks even here, and we can see the triggers.

[00:16:17]
So it was an idle, it fetched on idle and hydrated on the viewport active, then we have another one, defer here. That's our buy ticket. I have a question on the tabs as well. If you did use a component in the tab, do all the life cycle events run when you switch, like when you're using this content projection, would ng on init run again if you switch tabs or ng on destroy when you leave the tab, or do you need to be careful about that?

[00:16:58]
So when we content project this, right now, we have the tab group that goes over them at the same time and just renders them, so let's think about it. So right now, no, because it will have the ifs directly here. So if it's active, it shows, but if it's not active, it's there, but it doesn't show it, so all tabs are initialized. Yes, and each one has its own label, and you can see here we have one, two, three tabs.

[00:17:39]
All three tabs are here. It's just the content within this tab is deferred. Good question, yes. So switching the tabs will not trigger the new initialization for the tab itself, but for the, for some of this defer could be. So if we have a component here, for example, but then the active, so if false, it'll be removed. So we don't have like display none for it, it'll be truly removed from the HTML and then if it's active, the content will be there, so anything within this if would be recreated.

[00:18:18]
Yes, forgot. Where are you getting tab state? Where is that, library tab state coming from? Tab state is, side by side within our tab groups. Oh, that's the one that was first class that we created injectable. Yes, that's our service. It's our injectable that keeps track of the tab state. And it's tied to the life cycle of the tab group. So when that tab group is gone, there's no more tab service.

[00:19:01]
But these tabs themselves, are they coming from, wait, you have another TypeScript file for tabs? Yes, that's the tab. Yeah. So, basically, app tab. Is there anything native that you're importing about the tabs? Nope. Okay. There's nothing really. Nope. Ng content here, that's it, yeah, there's nothing native about them. So it's all style well Tailwind, all our speakers are styled with Tailwind as well.

[00:19:38]
Yeah, and this will work for all of them. Sure. Another speaker when we have one. Jessica was responsible for server-side rendering at Angular team. All these names are real. So, yeah, she did an amazing job with SSR. And signals, deep dive. We have another two speakers here Alex Rickabaugh and Pavel Kozlowski. They're basically the ones who were implementing the signals and a lot of things around them from the Angular team.

Learn Straight from the Experts Who Shape the Modern Web

  • 250+
    In-depth Courses
  • Industry Leading Experts
  • 24
    Learning Paths
  • Live Interactive Workshops
Get Unlimited Access Now