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.
<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.
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:
- A parent component with an outlet and a method to set children
- A child component that displays individual items
- A store that holds the data
Parent Component
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
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:
// 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!