#12 | Components, Layouts, & Partials - AdonisJS 7
Chapters7
The chapter contrasts two ways to reuse markup: Partials for simple reusable bits and Components for fragments with isolated state and slots, introducing how each fits into EdgeJS workflows.
AdonisJS 7 edge.js tips: split markup with partials and components, use slots and inject for clean, reusable UI in a grid of challenges.
Summary
Adocasts’ deep dive into AdonisJS 7 walks through organizing reusable UI with edge.js. Chris demonstrates when to use partials versus components: partials share the parent state and are ideal for simple, reusable markup; components have isolated state and require props for data. The tutorial shows moving an aggregation block into a partial located under resources/views/partials/challenge, then including it with edge.js’s include tag. For components, Chris extracts the grid and grid item into components under resources/views/components and then renders them with at component, passing challenges as props. The guide covers slots, including default and named slots, and explains how to render slot content with await slots.main, plus slot scopes to pass per-item data like challenge. He also introduces slot injection (at inject) to simplify passing data directly into slots, and highlights Edge.js’s automatic component registration for tags like grid and grid item. Visual Studio Code extension support is noted for snake_case to camelCase mapping, and the end tag rules differ for components versus built-in tags. Finally, Chris demonstrates debugging details, the need to refresh after code changes, and how to use the edge.js extension for autocomplete and navigation. The result is a cleaner, modular approach to building a challenges list with a responsive card grid while preserving the original behavior.
Key Takeaways
- Move complex markup into a partial (resources/views/partials/challenge/available_points.edge) and include it with edge.js include for cleaner parent pages.
- Convert a list into a grid by turning the unordered list into a grid component (grid.edge) and render via at component path.
- Pass data to components with props (second parameter object) so the component has access to challenges and can merge them into its isolated state.
- Use slots to render content inside a component, including a default main slot and optional named slots like after, with slot scopes to provide per-item data.
- Switch to slot injection (at inject) to simplify data flow from the parent into the slot content, removing the need for explicit slot scopes.
- Leverage Edge.js automatic tag registration under resources/views/components for easy usage like at grid or at grid.item via camelCase naming.
- Remember to end non-self-closing component tags with a generic end tag unless you mark them self-closing using the exclamation point (for slots).
Who Is This For?
Essential viewing for AdonisJS 7 developers who want to structure reusable UI with edge.js—partials, components, and slots, plus practical tips for clean file organization and VS Code workflow.
Notable Quotes
"Edge.js keeps to a snake case naming convention throughout its file structure."
—Introduction to how Edge.js filenames and paths map to tags.
"Edge.js has an include tag that allows us to include partials into our page."
—Demonstrating how to pull in reusable markup from a partial.
"Components have their own isolated state and need props to access data from the page."
—Explaining the difference between partials and components.
"Slots are child contents, and we can render them with await slots main to place content inside the grid component."
—Describing how to structure slot content inside components.
"We can skip slot scopes by using injected state with at inject to pass data directly into slot contents."
—Showing a simplified data flow via injection into slots.
Questions This Video Answers
- How do I use partials and components together in AdonisJS edge.js?
- What are edge.js slots and slot scopes and when should I use them?
- How do I pass data from a page to a component in AdonisJS 7?
- What is the difference between including a partial and rendering a component in edge.js?
- How can I enable autocomplete and tag usage for edge.js components in VS Code?
AdonisJS 7edge.jspartialscomponentsslotsslot scopesinjectgrid componentgrid item componentVisual Studio Code extension (edge.js)
Full Transcript
All right, pivoting for a brief bit. Let's talk about and focus on edge.js for a moment. There's two options whenever it comes to adding reusable markup partials and components. Components are reusable fragments with isolated state props and slots as we've seen previously with our layout whenever we were inspecting things inside of it. Partials, however, are straightforward reusable bits of markup that share the exact same state as their parent. They can however also be used to keep things clean as well. So for example, within our challenges index page, we have this bit right up here aggregating the available points for all of our challenges.
Let's extract this out into a partial instead for the sake of cleanliness. So again, you can use the ACLI, but what I'm going to do is just dive down into our resources views. And inside of here, there's a partials directory, and then we'll rightclick new file. And again, I'm going to put this inside of a specific challenge folder. And we'll call the file itself available_points. edge. Adonis.js keeps to a snake case naming convention throughout its file structure. So we'll keep to that here. Next, what we want to do is move the aggregation portion from our index page into this new partial that we've created as well as the div showing the total points.
So we'll cut those out and move them into here just like so. That now gives us this partial file to perform our aggregation and list out the total available points for the challenge itself in a very clean fashion. So, we'll give that a save. Jump back over into our challenges page because now we need to show this partial where we had previously had that div. To do this, Edge.js has an include tag that allows us to include partials into our page. And this accepts a path from our resources views directory. So, for this one, it's going to be partials challenge available points.
So partials challenge available points just like so. Edge.js is automatically going to know to look for the edge file extension. So we don't need that here. With the EDJS extension added into Visual Studio Code as well. We can verify that this is mapped and found that file via the little underscore here. This actually serves as a link as well. So for example, if I hold command and hover over it, you'll see that I can now click on it to dive straight into that partials file. And of course also note similar to let and assign this include tag is self-closing.
So we don't need to end it like we do our ifs and loops. Okay. So if we give this a save and jump back into our browser and let's head back into our challenges index page, we should see that everything looks the exact same as before. We still have our total points available and it's aggregating appropriately. The only difference is that we're now doing that in a little bit of a cleaner fashion. Now unlike our partials here, components actually don't share state. They instead have their own isolated state. Meaning that if we wanted to access challenges from our page, we would need to manually pass those into the component via what's called props.
Props we pass into the component are then merged into that isolated state of the component and made available for us to use. It's important to note though that components do still have access to global and local state. So for example, we would still have access to that app name global that we've defined as well as anything that we share with the view share from our controller that we've discussed previously as well. So for our components, let's focus on this unordered list that we have looping over all of our challenges. Similar to partials, our components have their own folder as well, which we've taken a look at previously for our layout component.
So within here, we can rightclick to create a new file. And we'll put this again inside of a challenge folder and call this actual file grid.edge. Hit enter to create that. And then we can move our unordered list from our index page. So I'm going to highlight the complete unordered list here. cut it out and plop that inside of our grid. All right, give that a save and then let's jump back into our index page and add it where we previously had this unordered list. So, similar to partials, HDS has a tag that we can use to include components in our page called at component.
This too accepts in a stringbased path from our resources views directory. So, for this it's going to be components challenge grid. So, components challenge grid just like so. Unlike our include tag, however, components do accept something called slots, which are child contents, and that's actually how we're providing our pages contents into our layout component. Ultimately, meaning that our component tag does need ended. So, we can use the specific end component or again just end there based on which you prefer. Now, in order for this grid to have access to our challenges here from our page, we need to provide those in as props.
Those go in as the second parameter here to our component function as an object. So we can add our challenges in there just like so. With that added, now our component has access to the challenges the same as we have on our page. These props will then get merged into the isolated state of our component itself. And although they are merged into our state and made accessible just as we have it here, we can also directly access our specific props via if I dump this out a dollar sign props variable on our state as well.
So if I give this a save here and we jump back into our browser, you'll notice that we have our dump right here with our component props. If we expand this a little bit, we'll see that we have challenges with our three challenges. And these component props also has a prototype on it with some helper functions made available as well. With this we can get all props. We can determine whether or not a prop has been provided. We can get a specific prop. Get a list of specific props. Get a list excluding specific props. Merge data into these props.
Conditionally merge data into these props via either merge if or merge unless. And then convert these props to HTML attributes via the two attributes function there. Okay. We can remove this dump then. So, I'm just going to and let's alter our markup here slightly, removing our unordered list, switching it to instead be a div. And I'm going to add a cards class, which is one of the predefined classes from our hypermedia starter kit. This will merely add a grid of three columns for our challenges to be displayed within. Next, let's add in another component to our challenges folder.
So, rightclick, new file, and we'll call this grid item.edge. This will be for each individual item that we're looping over inside of our grid. For this one, since we have gotten rid of our unordered list, we can omit the li and just pluck out the anchor tag that we have here and then remove the li al together and plop the anchor tag into our grid item. Give that a save. And now we need to add this component into our grid component for display. So we'll do at component and then reach through to components challenge/grid item.
Again, keeping with that snake cased naming convention. And now into the props of this component, we need to provide the individual challenge that we're looping over here. So that's just going to be challenge instead of challenges. And then of course, we also need to end our component. So with that saved, we should still have the exact same thing as before. It's just now that we've gotten rid of that li, things look a little bit better now. All right. Fantastic. So although the component tag is not self-closing, we can actually mark it as such by suffixing the at character with an exclamation point.
And this merely tells Edge.js to treat this component tag now as a self-closing tag, meaning that we can omit the manual end tag. Now, if you're wondering why the component tag is not self-closing like the include tag is in the first place, the reason is actually because components can utilize slots or child content. And we've seen this previously inside of our layout. By calling slots.main, we're telling the layout component here to render all of the content in between its start and end tag using edge.js. And then we get back that HTML markup here as the result.
This is also an asynchronous operation, so we do need to await it. Additionally, since it renders raw HTML, we're using three curly braces to render it as such, so that we don't get an HTML string literally plopped inside of our document. So, this initiates the slot content render and marks where it should be rendered inside of this component itself. And then the actual contents can live inside of our page between the start and end tag for that component. So, if we wanted to include our grid item here in between the component and end component for our grid and itself, we could absolutely do that.
So, let's scroll back up to our grid and cut out this component right here and instead use three curly braces await slots main to render out whatever we provide into the slot content of our grid component. So, here the dollar sign slots variable that we're using is an object that gets named slots that we can utilize inside of our component. The main slot that we're using here is the default slot and it's always going to be defined. We can however add additional name slots to this component if we have additional markup that we need to render elsewhere inside of this component using child content.
To do that, we just add it similarly. So await slots and then instead of doing main, we would just do dot and then whatever name we want to give this particular slot content. So since this is after our each list, we could do something like after there. Unlike main which is always provided, these additional name slots will be null if the implementation of this component doesn't actually provide content for this slot. So if these additional name slots are going to be optional, we do need to wrap them in an if just like so. And then of course end our if there when it comes to using our slots, then what we want to do is take that grid item that we cut out and paste it in as the default slot content for our grid.
With that in place, now whenever our grid loops over each challenge, it's going to render out that component to use as its main slot content. Meaning that we're going to get three different instances of our grid item component for each challenge that we're looping over. So, it's going to work the exact same as if we had added that component right here itself. Just this time, we're using slots. The only problem here is though, if we jump back into our index page, we don't have access to the challenge variable from that specific item that we're looping over inside of our grid.
One option that we have to fix this though is to use slot scopes. Slot scopes allow us to provide contextual information from inside of a component and pass it through to the slot content to use. To add this in, we can use edge.js's JS's at slot tag and specify the name of the slot that we want to make use of. This one's going to be our main slot again designated and matching to the method that we're calling inside of that component. And the slot content comes as the second argument here. So we can call that our slot scope.
And then we need to end the slot as well. Then within our grid, we need to provide the challenge that we're currently looping over into the slot scope so that it's accessible via that scope variable that we've defined. We can do this by providing an object into the main function. Anything that we provide in this object is going to be made available via the scope variable here from our slot tag. So if we provide in challenge here, give that a save, jump back into our index page. What we can now do is specify that our grid item should be passed a challenge from our scope.
Challenge value with that. Now everything should be working a okay. So we can jump back into our browser and you might see an error, but most likely this is going to be from a previous save where we had not had our slot scope set up yet. So I'm going to refresh this just to check. And sure enough, there we go. We get our page back. If you recall back, similar to the HTML page that we had before uh where we had not yet had VIT set up, the error pages aren't going to automatically refresh our contents if we happen to run into an error.
So, we do need to manually refresh in those instances to pick up new content. Now, one more thing about our slot tag. We have the after slot optionally available here within our grid as well. If we wanted to use that, we would use that similar to our main slot here by just adding in an additional slot tag pointing specifically to that after name. We're not providing any scope to it. So, we'll leave that off of our slot. We can then end this and then add in whatever here. With that saved, now after our grid, we're going to get whatever added in via that named slot.
All right, I'm going to go ahead and get rid of that. And we can get rid of this additional slot here as well. We aren't going to need it. And we can actually kick this up a notch and skip slot scopes altogether this middle main here by using injected state. So within our grid we can just use the at inject tag to inject the currently looped over challenge into its slot contents to be made automatically available via that slot contents context. So we can remove that slot scope. Give this a save. We're going to run into an error again.
That's okay. We'll fix it up here. And since we no longer need slot scopes, we can get rid of our main slot tag for the main slot. That slot tag is only needed anytime we need to use slot scopes. Then we can get rid of the props that we're providing into our component here for our grid item. And I'm actually going to just comment out our component for right now because similar to props, whenever we inject content into the slot, as we were doing right here, this is injecting whatever we provide here into the context of the slot.
So it's made available similar to props via a dollar sign context variable. So within our index page here, if we just go ahead and dump out dollar sign context, we can see exactly what we get. So we'll give that a save, jump back into our browser. Uh we're going to run into an error. That's fine. Let's go ahead and refresh to see what we get. All right. So you can see that we get three individual dumps, one per challenge that we're looping over. They look a little messed up, but that's that's a that's okay. We can expand it out to see exactly what we get.
So, we get back inside of each of these objects the particular challenge that we are looping over. And if I expand each of those out, you'll see that we get in each of our loops, it starts here and then it goes down to this one and then finally that one, that we get an individual context per item that we're looping over inside of our grid component. Meaning that if we now jump inside of our grid item component, and we can also get rid of our dump here, we can access the challenge that we're currently looping over inside of our parent grid component via that context.
So we can do a variable here called challenge. Reach through to that context and we're providing it on the context as challenge just like so. So with that saved, everything should now work perfectly fine. And we're skipping the slot content middleman by injecting it directly from our grid into our grid item. So jumping back into our browser again, I saved an inopportune moment that resulted in an error. But if we refresh, everything's now back to working. AOK. Okay. So, let's go ahead and rightclick and inspect on one of our grid items here. So, we have this cards div that's being rendered out by our grid component.
This grid that you see here isn't noting that in any way. That's just saying that this is using a CSS grid. But, this cards div is being rendered out by our grids component. And then each anchor inside of it is an individual render of our grid item component that's being done by looping over the challenges provided into the props of our grid component and being injected from that grid component into the slot context for each individual grid item we're looping over just to kind of give a visual of what exactly is going on there. Now any component that we've created inside of this resources views components directory actually automatically gets registered inside of edge.js as a tag.
This gives us a more convenient way to utilize components as well as cleaner markup to boot. And that's exactly why we're actually able to use our layout exactly as such. It resides within resources, views, components exactly as layout.edge. So we can use it as just at layout like a traditional tag for our more nested components here. For example, we have our grid inside of a challenge folder. All that we need to do is imagine the folder structure to the component file as a camelcase nested object. So, for example, if we wanted to use a tag for our grid, instead of doing at component with a path, we could just do at@ challenge, which is the name of the folder that we've put our grid component within, dot grid to access that particular component.
And you'll notice that via the edge.js extension in Visual Studio Code, we also get a nice autocomplete for these as well. Then, similar to our layout and other tags, we call this like a function. And then we can provide our props into this component similar to how we provided slot content by just adding an object into this function and passing it in just like so. So now we can get rid of this line altogether because it's doing exactly the same. And unlike the builtin tags in edgejs, these custom components don't get a custom end tag.
So we can't do end challenge.grid for example. Instead, that's where we need to just reach for the generic end tag. al together to end these out. And the self-closing designation works the exact same for these as well. So for example, for our grid item, we can switch this to instead reach for challenge, swap the slash for a dot, and then edge.js will automatically switch these tags from the file name snake case naming convention to the codebased camelc case naming convention for us to use as grid item there. Now an additional small note here is if we have a component at resources views components challenge index.edge, edge.
You can actually access that tag as just at challenge.
More from Adocasts
Get daily recaps from
Adocasts
AI-powered summaries delivered to your inbox. Save hours every week while staying fully informed.


