Concorde documentation (crawl) · interactive version

Concorde decorator — @autoSubscribe. > Legacy: prefer @subscribe + DataProviderKey. Examples below may still show PublisherManager for existing codebases. Doc ID: docs/_decorators/auto-subscribe. Keywords: Concorde, supersoniks, docs/_decorators/auto-subscribe, auto-subscribe, @autoSubscribe, decorator, autoSubscribe, DataProviderKey, PublisherManager, ; } render() { return html. URL: https://concorde.supersoniks.org/crawl/docs/_decorators/auto-subscribe.html.

@autoSubscribe

Legacy: prefer @subscribe + DataProviderKey. Examples below may still show PublisherManager for existing codebases.

The @autoSubscribe decorator automatically detects which publishers are accessed within a method and subscribes to them. When any of these publishers change, the method is automatically re-executed.

Principle

This decorator wraps a method to track which publishers are accessed during its execution. It then subscribes to all accessed publishers, and when any of them change, the method is re-executed. This provides automatic reactivity without manually managing subscriptions.

Usage

Import

import { autoSubscribe } from "@supersoniks/concorde/decorators";

Basic example

@customElement("demo-auto-subscribe")
export class DemoAutoSubscribe extends LitElement {
  static styles = [tailwind];

  @state() displayText: string = "";
  @state() computedValue: number = 0;

  @autoSubscribe()
  updateDisplay() {
    const value1 = PublisherManager.get("autoValue1").get() || 0;
    const value2 = PublisherManager.get("autoValue2").get() || 0;
    this.computedValue = value1 + value2;
    this.displayText = `${value1} + ${value2} = ${this.computedValue}`;
  }

  render() {
    return html`
      <p><strong>${this.displayText}</strong></p>
      <div>
        <sonic-button @click=${() => this.randomizeValue("autoValue1")}>
          Randomize Value 1
        </sonic-button>
        <sonic-button @click=${() => this.randomizeValue("autoValue2")}>
          Randomize Value 2
        </sonic-button>
      </div>
    `;
  }

  randomizeValue(publisherId: string) {
    const value = PublisherManager.get(publisherId);
    value.set(Math.floor(Math.random() * 100));
  }
}
<docs-demo-sources for="demo-auto-subscribe"></docs-demo-sources>
    <demo-auto-subscribe></demo-auto-subscribe>

Example with render method

@customElement("reactive-view")
export class ReactiveView extends LitElement {
  @autoSubscribe()
  render() {
    const data = PublisherManager.get("myData");
    const config = PublisherManager.get("config");

    // This render method will be automatically re-executed
    // when myData or config change
    const value = data.get()?.value || 0;
    const multiplier = config.get()?.multiplier || 1;

    return html`
      <div>
        <h1>Result: ${value * multiplier}</h1>
        <p>Value: ${value}</p>
        <p>Multiplier: ${multiplier}</p>
      </div>
    `;
  }
}

How it works

  1. First execution: When the method is called, PublisherManager.collectModifiedPublisher() is used to track which publishers are accessed
  2. Subscription: After execution, the decorator subscribes to all detected publishers
  3. Re-execution: When any subscribed publisher changes, the method is automatically called again
  4. Cleanup: On disconnectedCallback, all subscriptions are automatically removed

Behavior

Use cases

This decorator is particularly useful for:

Complete example

import { html, LitElement } from "lit";
import { customElement } from "lit/decorators.js";
import { autoSubscribe } from "@supersoniks/concorde/decorators";
import { PublisherManager } from "@supersoniks/concorde/core/utils/PublisherProxy";

@customElement("shopping-cart")
export class ShoppingCart extends LitElement {
  items: any[] = [];
  total: number = 0;
  discount: number = 0;

  @autoSubscribe()
  calculateTotal() {
    const cart = PublisherManager.get("cart");
    const promo = PublisherManager.get("promo");

    // Access cart items
    this.items = cart.items.get() || [];

    // Access promo code
    const promoCode = promo.code.get() || "";
    const discountPercent = promoCode === "SAVE10" ? 0.1 : 0;

    // Calculate totals
    const subtotal = this.items.reduce((sum, item) => 
      sum + (item.price * item.quantity), 0
    );
    this.discount = subtotal * discountPercent;
    this.total = subtotal - this.discount;

    this.requestUpdate();
  }

  connectedCallback() {
    super.connectedCallback();
    this.calculateTotal();
  }

  render() {
    return html`
      <div class="cart">
        <h2>Shopping Cart</h2>
        ${this.items.map(item => html`
          <div>${item.name} x${item.quantity} - ${item.price}€</div>
        `)}
        <div class="total">
          <p>Subtotal: ${this.total + this.discount}€</p>
          <p>Discount: -${this.discount}€</p>
          <p><strong>Total: ${this.total}€</strong></p>
        </div>
      </div>
    `;
  }
}

// When you update the publishers, calculateTotal is automatically called:
const cart = PublisherManager.get("cart");
cart.items.set([
  { name: "Product 1", price: 10, quantity: 2 },
  { name: "Product 2", price: 15, quantity: 1 }
]);

const promo = PublisherManager.get("promo");
promo.code.set("SAVE10");
// calculateTotal will be automatically called and the UI will update

Notes