The Next Evolution of GraphQL Front Ends
Tags: graphql, webcomponents, html, javascript
Published: 2021-08-02 14:32:20

---
description: 'Introducing the next evolution in GraphQL web apps with Apollo Elements: Reactive Controllers and dynamic HTML queries bring you more power and more flexibility.'
---

<aside>

*Originally posted on the [Apollo Elements blog](https://apolloelements.dev/blog/next-evolution/). Read there to enjoy interactive demos.*

</aside>


Apollo Elements has come a long way since its first release as `lit-apollo` in 2017. What started as a way to build GraphQL-querying LitElements has blossomed into a [multi-library](https://apolloelements.dev/api/libraries/), multi-paradigm project with [extensive docs](https://apolloelements.dev/api/).

Today we're releasing the next version of Apollo Elements' packages, including a major change: introducing GraphQL Controllers, and GraphQL HTML Elements.

## Reactive GraphQL Controllers
The latest version of [Lit](https://lit.dev) introduced a concept called "reactive controllers". They're a way to pack up reusable functionality in JavaScript classes that you can share between elements. If you've use JavaScript class mixins before (*not* the same as React mixins), they you're familiar with sharing code between elements. Controllers go one-better by being sharable and composable without requiring you to apply a mixin to the host element, as long as it implements the [`ReactiveControllerHost`](https://lit.dev/docs/composition/controllers/#controller-host-api) interface.

You can even have multiple copies of the same controller active on a given host. In the words of the Lit team, controllers represent a "has a _" relationship to the host element, where mixins represent an "is a _" relationship.

For Apollo Elements, it means now you can add many GraphQL operations to one component, like multiple queries or a query and a mutation. Here's an interactive example of the latter:

```ts
import type { TextField } from '@material/mwc-textfield';
import { ApolloQueryController, ApolloMutationController } from '@apollo-elements/core';
import { LitElement, html } from 'lit';
import { customElement, query } from 'lit/decorators.js';
import { UsersQuery, AddUserMutation } from './graphql.documents.js';
import { style } from './Users.css.js';

@customElement('users-view')
class UsersView extends LitElement {
  static styles = style;

  @query('mwc-textfield') nameField: TextField;

  users = new ApolloQueryController(this, UsersQuery);

  addUser = new ApolloMutationController(this, AddUserMutation, {
    awaitRefetchQueries: true,
    refetchQueries: [{ query: UsersQuery }],
  });

  onSubmit() { this.addUser.mutate({ variables: { name: this.nameField.value } }); }

  render() {
    const users = this.users.data?.users ?? [];
    const loading = this.users.loading || this.addUser.loading;
    return html`
      <form>
        <h2>Add a New User</h2>
        <mwc-textfield label="Name" ?disabled="${loading}"></mwc-textfield>
        <mwc-linear-progress indeterminate ?closed="${!loading}"></mwc-linear-progress>
        <mwc-button label="Submit" ?disabled="${loading}" @click="${this.onSubmit}"></mwc-button>
      </form>
      <h2>All Users</h2>
      <mwc-list>${users.map(x => html`
        <mwc-list-item noninteractive graphic="avatar">
          <img slot="graphic" ?hidden="${!x.picture}" .src="${x.picture}" role="presentation"/>
          ${x.name}
        </mwc-list-item>`)}
      </mwc-list>
    `;
  }
}
```

<aside>

[*View a Live Demo of this snippet*](https://apolloelements.dev/blog/next-evolution/#multiple-controllers)

</aside>

Controllers are great for lots of reasons. One reason we've found while developing and testing Apollo Elements is that unlike the class-based API of e.g. `@apollo-elements/lit-apollo` or `@apollo-elements/mixins`, when using controllers there's no need to pass in type parameters to the host class. By passing a [TypedDocumentNode](https://github.com/dotansimha/graphql-typed-document-node) object as the argument to the controller, you'll get that typechecking and autocomplete you know and love in your class template and methods, without awkward `<DataType, VarsType>` class generics.

If you're working on an existing app that uses Apollo Elements' base classes, not to worry, you can still `import { ApolloQuery } from '@apollo-elements/lit-apollo'`, We worked hard to keep the breaking changes to a minimum. Those base classes now use the controllers at their heart, so go ahead: mix-and-match query components with controller-host components in your app, it won't bloat your bundles.

We hope you have as much fun using Apollo Elements controllers as we've had writing them.

## Dynamic GraphQL Templates in HTML
The previous major version of `@apollo-elements/components` included `<apollo-client>` and `<apollo-mutation>`. Those are still here and they're better than ever, but now they're part of a set with `<apollo-query>` and `<apollo-subscription>` as well.

With these new elements, and their older sibling `<apollo-mutation>`, you can write entire GraphQL apps in nothing but HTML. You read that right, declarative, data-driven GraphQL apps in HTML. You still have access to the Apollo Client API, so feel free to sprinkle in a little JS here and there for added spice.

This is all made possible by a pair of libraries from the Lit team's Justin Fagnani called [Stampino](https://github.com/justinfagnani/stampino/) and [jexpr](https://github.com/justinfagnani/jexpr/). Together, they let you define dynamic parts in HTML `<template>` elements, filling them with JavaScript expressions based on your GraphQL data.

Here's the demo app from above, but written in HTML:

```html
<apollo-client>
  <apollo-query>
    <script type="application/graphql" src="Users.query.graphql"></script>
    <template>
      <h2>Add a New User</h2>
      <apollo-mutation refetch-queries="Users" await-refetch-queries>
        <script type="application/graphql" src="AddUser.mutation.graphql"></script>
        <mwc-textfield label="Name"
                       slot="name"
                       data-variable="name"
                       .disabled="{{ loading }}"></mwc-textfield>
        <mwc-button label="Submit"
                    trigger
                    slot="name"
                    .disabled="{{ loading }}"></mwc-button>
        <template>
          <form>
            <slot name="name"></slot>
            <mwc-linear-progress indeterminate .closed="{{ !loading }}"></mwc-linear-progress>
            <slot name="submit"></slot>
          </form>
        </template>
      </apollo-mutation>
      <h2>All Users</h2>
      <mwc-list>
        <template type="repeat" repeat="{{ data.users ?? [] }}">
          <mwc-list-item noninteractive graphic="avatar">
            <img .src="{{ item.picture }}" slot="graphic" alt=""/>
            {{ item.name }}
          </mwc-list-item>
        </template>
      </mwc-list>
    </template>
  </apollo-query>
</apollo-client>
<script type="module" src="components.js"></script>
```

<aside>

[*View a Live Demo of this snippet*](https://apolloelements.dev/blog/next-evolution/#html-components)

</aside>

There's a tonne of potential here and we're very keen to see what you come up with using these new components. Bear in mind that the stampino API isn't stable yet: there may be changes coming down the pipe in the future, but we'll do our best to keep those changes private.

## More Flexible HTML Mutations

The `<apollo-mutation>` component lets you declare GraphQL mutations in HTML. Now, the latest version gives you more options to layout your pages. Add a stampino template to render the mutation result into the light or shadow DOM. Use the `variable-for="<id>"` and `trigger-for="<id>"` attributes on sibling elements to better integrate with 3rd-party components, and specify the event which triggers the mutation by specifying a value to the `trigger` attribute.

```html
<link rel="stylesheet" href="https://unpkg.com/@shoelace-style/shoelace@2.0.0-beta.47/dist/themes/base.css">
<script src="https://unpkg.com/@shoelace-style/shoelace@2.0.0-beta.47/dist/shoelace.js?module" type="module"></script>

<sl-button id="toggle">Add a User</sl-button>

<sl-dialog label="Add User">
  <sl-input label="What is your name?"
            variable-for="add-user-mutation"
            data-variable="name"></sl-input>
  <sl-button slot="footer"
             type="primary"
             trigger-for="add-user-mutation">Add</sl-button>
</sl-dialog>

<apollo-mutation id="add-user-mutation">
  <script type="application/graphql" src="AddUser.mutation.graphql"></script>
  <template>
    <sl-alert type="primary" duration="3000" closable ?open="{{ data }}">
      <sl-icon slot="icon" name="info-circle"></sl-icon>
      <p>Added {{ data.addUser.name }}</p>
    </sl-alert>
  </template>
</apollo-mutation>
<script type="module" src="imports.js"></script>

<script type="module">
  const toggle = document.getElementById('toggle');
  const dialog = document.querySelector('sl-dialog');
  const mutation = document.getElementById('add-user-mutation');
  toggle.addEventListener('click', () => dialog.show());
  mutation.addEventListener('mutation-completed', () => dialog.hide());
</script>
```

<aside>

Demonstrating how to use <code>&lt;apollo-mutation&gt;</code> with [Shoelace](https://shoelace.style) web components. [*View a Live Demo of this snippet*](https://apolloelements.dev/blog/next-evolution/#shoelace)

</aside>


## Atomico support

On the heels of the controllers release, we're happy to add a new package to the roster. Apollo Elements now has first-class support for [Atomico](https://atomico.gitbook.io), a new hooks-based web components library with JSX or template-string templating.

```jsx
import { useQuery, c } from '@apollo-elements/atomico';
import { LaunchesQuery } from './Launches.query.graphql.js';

function Launches() {
  const { data } = useQuery(LaunchesQuery, { variables: { limit: 3 } });

  const launches = data?.launchesPast ?? [];

  return (
    <host shadowDom>
      <link rel="stylesheet" href="launches.css"/>
      <ol>{launches.map(x => (
        <li>
          <article>
            <span>{x.mission_name}</span>
            <img src={x.links.mission_patch_small} alt="Badge" role="presentation"/>
          </article>
        </li>))}
      </ol>
    </host>
  );
}

customElements.define('spacex-launches', c(Launches));
```

## FAST Behaviors

[FAST](https://fast.design) is an innovative web component library and design system from Microsoft. Apollo Elements added support for FAST in 2020, in the form of `Apollo*` base classes. The latest release transitions to FAST [Behaviors](https://www.fast.design/docs/api/fast-element.controller.addbehaviors), which are analogous to Lit `ReactiveControllers`.

```ts
@customElement({ name, styles, template })
class UserProfile extends FASTElement {
  profile = new ApolloQueryBehavior(this, MyProfileQuery);
  updateProfile = new ApolloMutationBehavior(this, UpdateProfileMutation, {
    update(cache, result) {
      cache.writeQuery({
        query: MyProfileQuery,
        data: { profile: result.data.updateProfile },
      });
    },
  });
}
```

The FAST team were instrumental in getting this feature over the line, so many thanks to them.

If you're already using `@apollo-elements/fast`, we recommend migrating your code to behaviors as soon as you're able, but you can continue to use the element base classes, just change your import paths to `/bases`. These may be removed in the _next_ major release, though.

```diff
-  import { ApolloQuery } from '@apollo-elements/fast/apollo-query';
+  import { ApolloQuery } from '@apollo-elements/fast/bases/apollo-query';
```

## New and Improved Docs
It wouldn't be an Apollo Elements release without some docs goodies. This time, in addition to new and updated docs and guides for components and controllers, we've replaced our webcomponents.dev iframes with `<playground-ide>` elements. All the "Edit Live" demos on this site, including the ones in this blog post, are running locally in your browser via a service worker. Talk about serverless, _amirite_?

The docs also got a major upgrade care of Pascal Schilp's untiring work in the [Webcomponents Community Group](https://www.w3.org/community/webcomponents/) to get the custom elements manifest v1 published. This latest iteration of the API docs generates package manifests directly from source code, and converts them to API docs via [Rocket](https://rocket.modern-web.dev).

## SSR

As part of the release, we updated our demo apps [leeway](https://leeway.apolloelements.dev) and [LaunchCTL](https://launchctl.apolloelements.dev). In the case of leeway, we took the opportunity to implement extensive SSR with the help of a new browser standard called [Declarative Shadow DOM](https://web.dev/declarative-shadow-dom/). It's early days for this technique but it's already looking very promising. You can try it out in any chromium browser (Chrome, Brave, Edge, Opera) by disabling JavaScript and visiting [https://leeway.apolloelements.dev](https://leeway.apolloelements.dev).

## Behind the Scenes

Bringing this release into the light involved more than just refactoring and updating the `apollo-elements/apollo-elements` repo. It represents work across many projects, including PRs to

- [Stampino](https://github.com/justinfagnani/stampino/pulls?q=is%3Apr+is%3Aclosed+merged%3A%3C2021-06-01+merged%3A%3E2021-01+) and [jexpr](https://github.com/justinfagnani/jexpr/pulls?q=is%3Apr+is%3Aclosed+merged%3A%3E2021-01), to iron out bugs, decrease bundle size, and add features
- [Hybrids](https://github.com/hybridsjs/hybrids/pull/167), to add support for reactive controllers
- [Atomico](https://github.com/atomicojs/hooks/pull/7) and [Haunted](https://github.com/matthewp/haunted/pull/239), to add the `useController` hook which underlies `useQuery` and co.

Additionally, here in apollo-elements, we added the [`ControllerHostMixin`](/api/libraries/mixins/controller-host-mixin/) as a way to maintain the previous element-per-graphql-document API without breaking backwards (too much). You can use this generic mixin to add controller support to any web component.

## Fixes and Enhancements

The last release included support for the web components hooks library [haunted](https://github.com/matthewp/haunted), but that support hid a dirty little secret within. Any time you called a hook inside a Haunted function component, apollo elements would sneakily mix the GraphQL interface onto the custom element's prototype. It was a good hack as long as you only call one hook per component, but would break down as soon as you compose multiple operations.

With controllers at the core, and the [`useController`](https://github.com/matthewp/haunted#usecontroller) hook, you can use as many Apollo hooks as you want in your elements without clobbering each other or polluting the element interface.

```js
import { useQuery, html, component } from '@apollo-elements/haunted';
import { client } from './client.js';
import { FruitsQuery } from './Fruits.query.graphql.js';
import { VeggiesQuery } from './Veggies.query.graphql.js';

customElements.define('healthy-snack', component(function HealthySnack() {
  const { data: fruits } = useQuery(FruitsQuery, { client });
  const { data: veggies } = useQuery(VeggiesQuery, { client });
  const snack = [ ...fruits?.fruits ?? [], ...veggies?.veggies ?? [] ];
  return html`
    <link rel="stylesheet" href="healthy-snack.css"/>
    <ul>${snack.map(x => html`<li>${x}</li>`)}</ul>
  `;
}));
```

<aside>

Demonstrating how to use multiple GraphQL hooks in a [haunted](https://hauntedhooks.netlify.app/) component. [*View a Live Demo of this snippet*](https://apolloelements.dev/blog/next-evolution/#haunted-multiple-hooks)

</aside>

The same is true of the [hybrids](https://hybrids.js.org) support, it now uses the controllers underneath the hood, letting you mix multiple operations in a single hybrid.

```js
import { query, html, define } from '@apollo-elements/hybrids';
import { client } from './client.js';
import { FruitsQuery } from './Fruits.query.graphql.js';
import { VeggiesQuery } from './Veggies.query.graphql.js';

define('healthy-snack', {
  fruits: query(FruitsQuery, { client }),
  veggies: query(VeggiesQuery, { client }),
  render(host) {
    const snack = [ ...host.fruits.data?.fruits ?? [], ...host.veggies.data?.veggies ?? [] ];
    return html`
      <link rel="stylesheet" href="healthy-snack.css"/>
      <ul>${snack.map(x => html`<li>${x}</li>`)}</ul>
    `;
  }
});
```

<aside>

Demonstrating how to use multiple GraphQL hooks in an [atomico](https://atomico.gitbook.io/) component. [*View a Live Demo of this snippet*](https://apolloelements.dev/blog/next-evolution/#haunted-multiple-hooks)

</aside>

## Try it Out

Apollo Elements next is available in prerelease on [npm](https://npm.im/@apollo-elements/core). We hope you enjoy using it and look forward to seeing what you come up with.

> Are you using Apollo Elements at work? Consider [sponsoring the project via Open Collective](https://opencollective.com/apollo-elements) to receive perks like priority support.