loading...
Cover image for Announcing Apollo Elements

Announcing Apollo Elements

bennypowers profile image Benny Powers ๐Ÿ‡ฎ๐Ÿ‡ฑ๐Ÿ‡จ๐Ÿ‡ฆ ใƒปUpdated on ใƒป5 min read

ื‘ืกืดื“

Today I published version 0.0.1 of apollo-elements, a collection of packages that make it easy to create web components connected to Apollo GraphQL1.

GitHub logo apollo-elements / apollo-elements

๐Ÿš€๐ŸŒ› Use the Launch Platform ๐Ÿ‘ฉโ€๐Ÿš€๐Ÿ‘จโ€๐Ÿš€

Rocket Ship in Angle Brackets

๐Ÿš€ Apollo Elements ๐Ÿ‘ฉโ€๐Ÿš€

๐Ÿš€ Custom elements meet Apollo GraphQL ๐ŸŒœ

๐Ÿ‘ฉโ€๐Ÿš€ It's one small step for a dev, one giant leap for the web platform! ๐Ÿ‘จโ€๐Ÿš€

Maintained with Lerna Contributions Welcome Actions Status Test Coverage Demo Online

๐Ÿ““ Contents

๐Ÿ“‘ API Docs

If you just want to see the API Docs, check them out for all our packages at apolloelements.dev

๐Ÿค– Demo

#leeway is a progressive web app that uses lit-apollo to make it easier for you to avoid doing actual work. Check out the source repo for an example of how to build apps with Apollo Elements. The demo includes:

  • SSR
  • Code Splitting
  • Aggressive minification, including lit-html template literals
  • CSS-in-CSS ( e.g. import shared from '../shared-styles.css';)
  • GQL-in-GQL ( e.g. import query from './my-component-query.graphql';)
  • GraphQL Subscriptions over websocket

Lighthouse Scores: 98 (performance), 100 (accessibility), 93 (best practises), 100 (SEO), 12/12 (PWA)

๐Ÿ“ฆ Packages

โ€ฆ

Apollo Elements handles the plumbing between web components libraries like lit-element or hybrids and Apollo Client, so you can concentrate on building your app.

If you're new to web components, take a ๐Ÿ‘€ at my "Let's Build Web Components" series to get up to speed:

Connected Components

In a component-based app, each component can derive its state from some separate state storage. For example, you can create components which connect to a Redux or MobX store and subscribe to changes to their piece of the state puzzle.

GraphQL's flexible and extensible syntax is a natural fit for component based design, and Apollo's powerful implementation lets us easily make the connection between GraphQL data and our components. Using apollo-link-state, you can even get rid of client-side state containers like redux altogether and query your entire component state from the apollo cache.

query UserProfilePage($userId: ID) {
  session @client {
    token
    expiresAt
    id
  }

  user(id: $id) {
    name
    avatar
    friends {
      name
      id
    }
  }
}

Show Me The Code

Now, with Apollo Elements, it's easy to get up and running building connected components. You provide a GraphQL document and a custom element class2 that handles templating, and you're good to go.

import { ApolloQuery, html } from '@apollo-elements/lit-apollo';

// A component that uses ApolloSubscription to update
// when users go on or offline.
import './user-online-status.js';
import './loading-spinner.js';

/**
 * `<user-profile-page>` Shows a user's profile, as well as a list
 * of their friends which display's each one's online status via a
 * GraphQL subscription.
 * @extends ApolloQuery
 */
class UserProfilePage extends ApolloQuery {
  render() {
    const { loading, data } = this;
    return (
      (loading || !data) ? html`<loading-spinner></loading-spinner>`
      !data.user ? : html`<login-form></login-form>`
    : html`
        <h1>Hello, ${data.user.name}</h1>
        <profile-image src="${data.user.avatar}"></profile-image>
        <h2>Who's Online?</h2>
        <dl>${data.user.friends.map(({id, name}) => html`
          <dt>${name}</dt>
          <dd><user-online-status id="${id}"></user-online-status></dd>
        `)}</dl>
      `
    );
  }

  updated() {
    // get the currently logged-in user's id from the `@client` query.
    const { id } = this.id;
    // setting variables updates the query.
    if (id) this.variables = { id };
  }
}

customElements.define('user-profile-page', UserProfilePage);

Once you've added your elements to the page, just set their query property to a GraphQL document and you're set.

Inline GraphQL Scripts

You can even do neat little optional tricks like defining your queries declaratively in HTML with inline GraphQL:

<user-profile-page>
  <script type="application/graphql">
    query UserProfilePage($userId: ID) {
      session @client {
        token
        expiresAt
        id
      }

      user(id: $id) {
        name
        avatar
        friends {
          name
          id
        }
      }
    }
  </script>
</user-profile-page>

Cute, right?

Support for Multiple Web Component Libraries

Apollo Elements grew out of lit-apollo, but I wasn't content with supporting a single component class. Version 0 shipped, renamed and rebranded, with support for lit-element, GluonElement, and hybrids. It also's got some Polymer-style two-way binding elements so you can expose the apollo state in your templates with {{data}} syntax.

But the goal here was to give you, the developer, more options. If none of the above are what you're looking for, Apollo Elements also provides class mixins that let you get up and running with any vanilla-like component class, or even good-old-fashioned HTMLElement, if you really wanted.

import { ApolloMutationMixin } from '@apollo-elements/mixins';
import gql from 'graphql-tag';

const mutation = gql`
  mutation SendMessage($message: String!) {
    sendMessage(message: $message) {
      message
      date
    }
  }
`;

const template = document.createElement('template');
template.innerHTML = `
  <style>
    :host([error]) #input {
      border: 1px solid red;
    }

    details {
      display: none;
    }

    :host([error]) details {
      display: block;
    }
  </style>
  <label>Message<input id="input"/></label>
  <details>
    <summary>Error!</summary>
    <span id="error"></span>
  </details>
`;

class ChatInput extends ApolloMutationMixin(HTMLElement) {
  get input() {
    return this.shadowRoot && this.shadowRoot.getElementById('input');
  }

  constructor() {
    super();
    this.mutation = mutation;
    this.onKeyup = this.onKeyup.bind(this);
    this.attachShadow({ mode: 'open' })
    this.shadowRoot.appendChild(template.content.cloneNode(true))
    this.input.addEventListener('keyup', this.onKeyup)
  }

  onKeyup({ key, target: { value: message } }) {
    if (key !== 'Enter') return;
    this.variables = { message };
    this.mutate();
  }

  // Override implementation of `onCompleted` if desired.
  // Alternatively, use a setter.
  onCompleted({ message, date }) {
    this.input.value = '';
  }

  // Override implementation of `onError` if desired.
  // Alternatively, use a setter.
  onError(error) {
    this.setAttribute('error', error);
    this.shadowRoot.getElementById('error').textContent =
      `Error when sending message: ${ error }`;
  }
}

Plans are in the works to support even more web component libraries and frameworks in the future, so watch the repo for releases.

So go ahead - install the package which fits your project best:

Demo

Want to see it in action? I built a simple chat app demo that uses GraphQL subscriptions and renders it's components using lit-element.

#leeway is a progressive web app that uses lit-apollo to make it easier for you to avoid doing actual work. Check out the source repo for an example of how to build apps with Apollo Elements. The demo includes:

  • SSR
  • Code Splitting
  • Aggressive minification, including lit-html template literals
  • CSS-in-CSS ( e.g. import shared from '../shared-styles.css';)
  • GQL-in-GQL ( e.g. import query from './my-component-query.graphql';)
  • GraphQL Subscriptions over websocket

Lighthouse Scores: 98 (performance), 100 (accessibility), 93 (best practises), 100 (SEO), 12/12 (PWA)

So try out apollo-elements today!

Footnotes
  • 1Apollo Elements is a community project maintained by myself, it's not affiliated with Meteor.
  • 2Or in the case of hybrids, all you need is a render function.

Discussion

pic
Editor guide
Collapse
hyperpress profile image
John Teague

Very cool, Benny. I really want to learn more about GraphQL and Apollo looks like a nice way into that.

Collapse
bennypowers profile image
Collapse
tzelonmachluf profile image
Tzelon Machluf

Good Job Benny.
ืื”ื‘ืชื™ ืืช ื”ื‘ืก"ื“ :)