SPA Routing
htag provides a built-in, lightweight Hash-Based Router (Router) for building Single Page Applications (SPAs). It allows you to define multiple "pages" as standard components and switch between them seamlessly while maintaining browser history.
Why Hash-Based?
htag uses URL hashes (e.g., #/about) for routing. This approach works perfectly with all runners and static file serving without requiring complex server-side redirects (catch-all routes).
Basic Usage
The Router is a component (Tag.div) that acts as an "outlet" for your pages.
from htag import Tag, Router, ChromeApp
class HomePage(Tag.div):
def init(self):
self <= Tag.h1("Welcome Home")
self <= Tag.a("Check Page 1", _href="#/page1")
class Page1(Tag.div):
def init(self):
self <= Tag.h1("Page 1")
self <= Tag.a("Back to Home", _href="#/")
class MyApp(Tag.App):
def init(self):
# 1. Create a Router
self.router = Router()
# 2. Add your routes (Component classes)
self.router.add_route("/", HomePage)
self.router.add_route("/page1", Page1)
# 3. Add the router to the UI
self <= self.router
if __name__ == "__main__":
ChromeApp(MyApp).run()
Dynamic Parameters
You can define routes with parameters using the :name syntax. These parameters are automatically extracted from the URL and injected into your component's init() method as keyword arguments.
class ProfilePage(Tag.div):
def init(self, user_id: str):
self.id = user_id
self <= Tag.h2(f"User Profile: {user_id}")
def on_mount(self):
print(f"Loading data for user {self.id}...")
# Registering the route
router.add_route("/users/:user_id", ProfilePage)
Navigating to #/users/manatlan will instantiate ProfilePage(user_id="manatlan").
Automatic Lifecycle Management
The Router handles the full component lifecycle automatically. When you navigate to a new route:
1. The outgoing component's on_unmount() is called (if it exists).
2. The outlet is cleared.
3. The new component is instantiated.
4. The new component's on_mount() is called.
This makes it easy to clean up resources (intervals, watchers) or fetch fresh data on every page change.
Programmatic Navigation
To navigate from Python without waiting for a user click, use router.navigate():
This method updates the browser hash, ensuring that the Back/Forward buttons continue to work as expected.
Custom 404 Pages
If no route matches the current path, a default "404 Not Found" message is displayed. You can customize this by providing your own component:
class MyNotFound(Tag.div):
def init(self, path=None):
self <= Tag.h1("Ouch! Where are we?")
self <= Tag.p(f"I couldn't find path: {path}")
router.set_not_found(MyNotFound)