---
# System prepended metadata

title: Tags VS Class API

---

# Tags VS Class API

## TL;DR

* Developer Experience
    * Fewer concepts to learn and less boilerplate
    * Encourages safer and more correct patterns
    * Optimized for colocation, refactoring and componentization
* User Experience
    * Only interactive code is bundled for the browser
    * Server work is not re-executed on the browser
    * Performant fine-grained updates instead of vdom/diffing

## Examples

### Sync input.title to document.title

<div class="comparison">

```jsx
/* tags */

<const/{ title } = input/>
<effect() {
  document.title = title;
}/>
```
```jsx
/* class */

class {
  onMount() {
    this.setTitle();
  }
  onUpdate() {
    this.setTitle();
  }
  setTitle() {
    if (this.previousTitle !== this.input.title) {
      document.title = this.previousTitle = this.input.title;
    }
  }
}
```

</div>

The tags api here is less code, but also less likely to introduce a bug.
It's easy for developers to forget to guard updates in the class api, leading to unecessary work or bugs.

### Sync input.title to document.title (with cleanup)

<div class="comparison">

```jsx
/* tags */

<const/{ title } = input/>
<effect() {
  const originalTitle = document.title;
  document.title = title;

  $signal.onabort = () => {
    document.title = originalTitle;
  }
}/>
```
```jsx
/* class */

class {
  onMount() {
    this.originalTitle = document.title;
    this.syncTitle();
  }
  onUpdate() {
    this.syncTitle();
  }
  onDestroy() {
    document.title = this.originalTitle;
  }
  syncTitle() {
    const { title } = this.input;
    if (title !== this.previousTitle) {
      document.title = this.previousTitle = title;
    }
  }
}
```

</div>

It's also easier in the tags api to colocate the cleanup functionality (when a component is destroyed) with the rest of the feature code.

### Use input to open and close a native dialog

<div class="comparison">

```jsx
/* tags */

<const/{ open } = input/>
<effect() {
  if (open) {
      el().showModal();
  } else {
      el().close();
  }
}/>

<dialog/el>
  <p>Greetings, one and all!</p>
</dialog>
```
```jsx
/* class */

class {
    onMount() {
        this.syncOpen();
    }
    onUpdate() {
        this.syncOpen();
    }
    syncOpen() {
        const { open } = this.input;
        if (open !== this.previousOpen) {
          this.previousOpen = open;
          if (open) {
              this.getEl("el").showModal();
          } else {
              this.getEl("el").close();
          }
        }
    }
}

<dialog key="el">
  <p>Greetings, one and all!</p>
</dialog>
```

</div>

In the class api with this example we spread things across multiple lifecycle events which can lead to problems when other code is in those lifecycle events. So now lets create a component that merges this dialog logic _and_ the title logic from earlier.

### Combined Examples

<div class="comparison">

```jsx
/* tags */

// Title functionality
<const/{ title } = input/>
<effect() {
  const originalTitle = document.title;
  document.title = title;

  $signal.onabort = () => {
    document.title = originalTitle;
  }
}/>

// Dialog functionality
<const/{ open } = input/>
<effect() {
  if (open) {
      el().showModal();
  } else {
      el().close();
  }
}/>

<dialog/el>
  <p>Greetings, one and all!</p>
</dialog>
```
```jsx
/* class */

class {
  onMount() {
    // Title functionality
    this.originalTitle = document.title;
    this.syncTitle();
  
    // Dialog functionality
    this.syncOpen();
  }
  onUpdate() {
    // Title functionality
    this.syncTitle();
  
    // Dialog functionality
    this.syncOpen();
  }
  onDestroy() {
    // Title functionality
    document.title = this.originalTitle;
  }
  // Dialog functionality
  syncOpen() {
    const { open } = this.input;
    if (open !== this.previousOpen) {
      this.previousOpen = open;
      if (open) {
          this.getEl("el").showModal();
      } else {
          this.getEl("el").close();
      }
    }
  }
  // Title functionality
  syncTitle() {
    if (this.previousTitle !== this.input.title) {
      document.title = this.previousTitle = this.input.title;
    }
  }
}
    
// Dialog functionality
<dialog key="el">
   <p>Greetings, one and all!</p> 
</dialog>
```

</div>
  
The dialog and title related features are interspersed in the class api lifecycle methods. This makes it hard to tell where the code for either feature is and a challenge to refactor out those features later.

With the tags API you can cut and paste either feature into a separate Marko file without having to move around any code.
  
## Take Away

The tags api may look different from the class api, but it's mostly about moving "state" into variables exposed from tags. It allows colocating state/lifecycle logic with your markup which makes code easier to write, read, reason-about and refactor.

...And it means the compiler can better understand your code and deliver optimized experiences to the user.


<style>
    .comparison {
        display: grid;
        grid-template-columns: repeat(auto-fit, minmax(25rem, 1fr));
        gap: 1rem;
        pre {
            margin: 0;
        }
    }
</style>