Bounded Properties

The ElementController and PageController classes enable the definition of properties whose values are bound to a communication channel. These bindings can be of three types: inbound, outbound, or both.

  • Inbound properties: In this type, the property value is injected into the controller by the channel. This allows the controller to receive and use data updates from the channel.

  • Outbound properties: Here, the property value updates within the controller are published to the channel. This ensures that any changes made to the property within the controller are immediately propagated to all other components or systems subscribed to the channel.

  • Inbound & Outbound properties: A property can simultaneously have both inbound and outbound bindings. This means the property can receive updates from the channel and also publish its updates to the channel. This bidirectional communication ensures synchronization between the controller and the channel, maintaining consistency across the system.

By leveraging these binding mechanisms, ElementController and PageController facilitate dynamic and responsive data handling within the application, promoting efficient communication and state management.

The controller utilizes the web component lifecycle hooks connectedCallback and disconnectedCallback to manage the connection and disconnection of bound properties to their respective channels. When the component is added to the DOM, the connectedCallback is invoked, allowing the controller to establish bindings for its properties to the designated channels. Conversely, when the component is removed from the DOM, the disconnectedCallback is triggered, ensuring that the properties are properly disconnected from the channels, thus preventing memory leaks and unwanted data updates.

In the context of LitElement, inbound properties can trigger a re-render by invoking Lit's requestUpdate method. This mechanism ensures that any updates received from the channels are reflected in the component's UI, maintaining a seamless and dynamic user experience.

The bounded properties in the controller are declared using the static fields inbounds and outbounds.

For inbound properties, the inbounds field specifies the following properties:

  • channel: This mandatory property defines the name of the channel from which data is read.
  • skipUpdate: This optional property, when set to true, prevents the triggering of requestUpdate on value changes. By default, it is set to false.
  • action: This optional property is a function that processes the incoming value from the channel and returns a transformed value.

Example:

export class CategoryPage extends (LitElement) {
  pageController = new PageController(this);

  static inbounds = {
    categoriesList: { channel: 'categories' },
    likedRecipes: { channel: 'liked-recipes', action: (data) => data.recipes.filter(r => r.hasLikes ) },
  };

For outbound properties, the outbounds field includes:

  • channel: This mandatory property specifies the name of the channel to which data is written.

Example:

export class CategoryPage extends (LitElement) {
  pageController = new PageController(this);

  static outbounds = {
    likedRecipes: { channel: 'liked-recipes' },
  };

A property can be declared in both the inbounds and outbounds fields, allowing it to be both read from and written to the respective channels.

Example:

export class CategoryPage extends (LitElement) {
  pageController = new PageController(this);

  static inbounds = {
    likedRecipes: { channel: 'user-liked-recipes', action: (data) => data.recipes.filter(r => r.hasLikes ) },
  };

  static outbounds = {
    likedRecipes: { channel: 'liked-recipes' },
  };

As you can see, the bounded properties provided by ElementController and PageController offer a convenient and concise way to achieve the same functionality as using @property or @state with manual publish and subscribe mechanisms.

For instance, this example:

export class CategoryPage extends (LitElement) {
  pageController = new PageController(this);

  static inbounds = {
    likedRecipes: { channel: 'liked-recipes' },
  };

is equivalent to the following implementation:

export class CategoryPage extends (LitElement) {
  pageController = new PageController(this);

  @state()
  protected _likedRecipes: Set<Recipe> | null = null;

  connectedCallback() {
    super.connectedCallback();

    this.pageController.subscribe('liked-recipes', (data: Set<Recipe>) => {
      this._likedRecipes = data;
      this.requestUpdate();
    });
  }

  disconnectedCallback() {
    this.pageController.unsubscribe('liked-recipes');
    super.disconnectedCallback();
  }
};