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:

vala
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:

vala
// 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:

vala
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:

  1. Collects all matching PageTemplate instances sorted by prefix depth
  2. Renders each template in order
  3. Finds <spry-template-outlet/> elements in each template
  4. Nests the content into the outlet, building the complete document
  5. Returns the final HTML as an HttpResult

You typically don't need to override handle_request() - the default implementation handles everything for you.

Next Steps