Your First Component

Catalyst's @controller decorator

Catalyst's @controller decorator lets you create Custom Elements with virtually no boilerplate, by automatically calling customElements.register, and by adding "Actions" and "Targets" features described later. Using TypeScript (with decorators support enabled), simply add @controller to the top of your class:

import {controller} from '@github/catalyst'

@controller
class HelloWorldElement extends HTMLElement {
  connectedCallback() {
    this.innerHTML = 'Hello World!'
  }
}

Catalyst will automatically convert the classes name; removing the trailing Element suffix and lowercasing all capital letters, separating them with a dash.

By convention Catalyst controllers end in Element; Catalyst will omit this when generating a tag name. The Element suffix is not required - just convention. All examples in this guide use Element suffixed names.

Remember! A class name must include at least two CamelCased words (not including the Element suffix). One-word elements will raise exceptions. Example of good names: UserListElement, SubTaskElement, PagerContainerElement

What does @controller do?

The @controller decorator ties together the various other decorators within Catalyst, plus a few extra conveniences such as automatically registering the element, which saves you writing some boilerplate that you'd otherwise have to write by hand. Specifically the @controller decorator:

  • Derives a tag name based on your class name, removing the trailing Element suffix and lowercasing all capital letters, separating them with a dash.
  • Calls window.customElements.define with the newly derived tag name and your class.
  • Calls defineObservedAttributes with the class to add map any @attr decorators. See attrs for more on this.
  • Injects the following code inside of the connectedCallback() function of your class:
    • bind(this); ensures that as your element connects it picks up any data-action handlers. See actions for more on this.
    • autoShadowRoot(this); ensures that your element loads any data-shadowroot templates. See rendering for more on this.
    • initializeAttrs(this); ensures that your element binds any data-* attributes to props. See attrs for more on this.

You can do all of this manually; for example here's the above HelloWorldElement, written without the @controller annotation:

import {bind, autoShadowRoot, initializeAttrs, defineObservedAttributes} from '@github/catalyst'
class HelloWorldElement extends HTMLElement {
  connectedCallback() {
    autoShadowRoot(this)
    initializeAttrs(this)
    this.innerHTML = 'Hello World!'
    bind(this)
  }
}
defineObservedAttributes(HelloWorldElement)
window.customElements.define('hello-world', HelloWorldElement)

The @controller decorator saves on having to write this boilerplate for each element.

What about without TypeScript Decorators?

If you don't want to use TypeScript decorators, you can use controller as a regular function by passing it to your class:

import {controller} from '@github/catalyst'

controller(
  class HelloWorldElement extends HTMLElement {
    //...
  }
)