Outlets

Outlets are placeholders in your component templates where child components can be dynamically inserted. They enable powerful component composition patterns, especially for lists and dynamic content.

What are Outlets?

An outlet is a special element in your markup that acts as a insertion point for child components. Think of it like a socket where you can plug in different components at runtime.

HTML
<div sid="list">
    <!-- This outlet will be filled with child components -->
    <spry-outlet sid="items"/>
</div>

Using set_outlet_children()

The set_outlet_children(sid, children) method populates an outlet with child components. Call it in your prepare() method after creating child components with the ComponentFactory.

Vala
public override async void prepare() throws Error {
    var factory = inject<ComponentFactory>();
    var store = inject<TodoStore>();

    // Create child components for each item
    var children = new Series<Renderable>();
    foreach (var item in store.items) {
        var component = factory.create<TodoItemComponent>();
        component.item_id = item.id;  // Pass data to child
        children.add(component);
    }

    // Populate the outlet
    set_outlet_children("items", children);
}

Parent/Child Component Pattern

The typical pattern for outlets involves:

  1. A parent component with an outlet and a method to set children
  2. A child component that displays individual items
  3. A store that holds the data

Parent Component

Vala
public class TodoListComponent : Component {
    private TodoStore store = inject<TodoStore>();
    private ComponentFactory factory = inject<ComponentFactory>();

    public override string markup { get {
        return """
        <div sid="list" class="todo-list">
            <ul sid="items">
                <spry-outlet sid="items-outlet"/>
            </ul>
        </div>
        """;
    }}

    public override async void prepare() throws Error {
        var children = new Series<Renderable>();
        foreach (var item in store.items) {
            var component = factory.create<TodoItemComponent>();
            component.item_id = item.id;
            children.add(component);
        }
        set_outlet_children("items-outlet", children);
    }
}

Child Component

Vala
public class TodoItemComponent : Component {
    private TodoStore store = inject<TodoStore>();
    private HttpContext http_context = inject<HttpContext>();

    public int item_id { get; set; }  // Set by parent

    public override string markup { get {
        return """
        <li sid="item" class="todo-item">
            <span sid="title"></span>
            <button spry-action=":Toggle" spry-target="item">Toggle</button>
        </li>
        """;
    }}

    public override void prepare() throws Error {
        var item = store.get_by_id(item_id);
        this["title"].text_content = item.title;
        // Pass item_id via hx-vals for handle_action
        this["item"].set_attribute("hx-vals", @"{\"id\":$item_id}");
    }

    public async override void handle_action(string action) throws Error {
        var id = int.parse(http_context.request.query_params.get_any_or_default("id"));
        if (action == "Toggle") {
            store.toggle(id);
        }
    }
}

Creating Lists with Outlets

Outlets are perfect for rendering dynamic lists. Here's the complete pattern:

Vala
// 1. Define a store to hold data
public class TodoStore : Object {
    public Series<TodoItem> items { get; default = new Series<TodoItem>(); }

    public void add(string title) { /* ... */ }
    public void toggle(int id) { /* ... */ }
    public void remove(int id) { /* ... */ }
}

// 2. Register as singleton
application.add_singleton<TodoStore>();

// 3. Parent component uses outlet
public class TodoListComponent : Component {
    private TodoStore store = inject<TodoStore>();

    public override string markup { get {
        return "<ul><spry-outlet sid="items"/></ul>";
    }}

    public override async void prepare() throws Error {
        var children = new Series<Renderable>();
        foreach (var item in store.items) {
            var child = factory.create<TodoItemComponent>();
            child.item_id = item.id;
            children.add(child);
        }
        set_outlet_children("items", children);
    }
}

// 4. Child component handles individual items
public class TodoItemComponent : Component {
    public int item_id { get; set; }
    // ... markup and methods
}

Outlets vs <spry-component>

When should you use outlets vs declarative child components?

Feature <spry-outlet> <spry-component>
Use Case Dynamic lists, multiple items Single, known child components
Population set_outlet_children() in prepare() Automatic, configured via properties
Access Not directly accessible get_component_child()
Examples Todo lists, data tables, feeds Headers, sidebars, fixed sections

Live Demo: Todo List

This interactive demo shows outlets in action. The todo list uses an outlet to render individual todo items. Try adding, toggling, and deleting items!

TodoListDemo

Todo List

0 items

    Next Steps