Page Components
Combine Component and Endpoint into a single powerful abstraction
What are Page Components?
A PageComponent is a special type of component that acts as both a
Component AND an Endpoint. This means it can render
HTML templates and handle HTTP requests at a specific route.
In traditional web frameworks, you often need separate classes for routes/endpoints and for rendering views. PageComponent eliminates this duplication by combining both responsibilities into a single cohesive class.
PageComponent vs Component
Understanding when to use PageComponent vs Component is essential:
| Feature | Component | PageComponent |
|---|---|---|
| Has markup | ✓ Yes | ✓ Yes |
| Handles HTTP routes | ✗ No | ✓ Yes |
| Can be nested in outlets | ✓ Yes | ✗ No (top-level only) |
| Implements Endpoint | ✗ No | ✓ Yes |
| Wrapped by templates | ✗ No | ✓ Yes |
When to Use Each
Use PageComponent when:
- Creating top-level pages (routes)
- Building documentation pages
- Rendering full HTML documents
- The component needs its own URL
Use Component when:
- Building reusable UI elements
- Creating widgets or cards
- Nesting inside other components
- Rendering partial content
Basic PageComponent Example
Here's how to create a simple PageComponent:
using Spry;
using Inversion;
public class DocumentationPage : PageComponent {
// Dependency injection works the same as Component
private ComponentFactory factory = inject<ComponentFactory>();
public override string markup { get {
return """
<div class="page-content">
<h1>My Documentation Page</h1>
<p>This page handles its own route.</p>
</div>
""";
}}
public override async void prepare() throws Error {
// Set up page content
}
}
Route Registration
PageComponents are registered as endpoints in your application's Main.vala. You register both the component type and its route:
// In Main.vala:
// Register the page component with the dependency container
application.add_transient<DocumentationPage>();
// Register the page as an endpoint at a specific route
application.add_endpoint<DocumentationPage>(
new EndpointRoute("/docs/page")
);
// Now visiting /docs/page will render DocumentationPage
// automatically wrapped by any matching PageTemplate
Dependency Injection in Pages
PageComponents support the same dependency injection patterns as regular components.
Use inject to access services, stores, and factories:
public class UserProfilePage : PageComponent {
// Inject services you need
private ComponentFactory factory = inject<ComponentFactory>();
private UserStore user_store = inject<UserStore>();
private HttpContext http_context = inject<HttpContext>();
public override string markup { get {
return """
<div class="profile-page">
<h2 sid="username"></h2>
<p sid="email"></p>
</div>
""";
}}
public override async void prepare() throws Error {
// Access route parameters from http_context
var user_id = http_context.request.query_params
.get_any_or_default("id", "guest");
// Use injected store to fetch data
var user = yield user_store.get_user(user_id);
// Populate template
this["username"].text_content = user.name;
this["email"].text_content = user.email;
}
}
Template Wrapping
One of the most powerful features of PageComponents is automatic template wrapping.
When a PageComponent renders, it is automatically wrapped by matching
PageTemplate instances.
This means your PageComponent markup only needs to contain the content
of the page, not the full HTML document structure. The template provides the
, , ,
navigation, and footer.
💡 Tip: Learn more about templates in the Page Templates documentation.
How handle_request() Works
The PageComponent base class implements the Endpoint
interface's handle_request() method. This method:
- Collects all matching
PageTemplateinstances sorted by prefix depth - Renders each template in order
- Finds
<spry-template-outlet/>elements in each template - Nests the content into the outlet, building the complete document
- Returns the final HTML as an
HttpResult
You typically don't need to override handle_request() - the default
implementation handles everything for you.