Apollo Elements Apollo Elements Guides API Blog Toggle darkmode

Usage

Once you've gotten set up, it's time to start building your app's components. What does building an app with GraphQL look like? What kinds of components does it have? This introductory guide will help you understand what it's like to build with Apollo Elements.

The Concept

In a GraphQL app, you'll use queries and mutations to interact with your application's data graph. GraphQL helps you model your frontend's data in terms of queries.

GraphQL: Query to Data
query Users {
  users {
    id
    name
    picture
  }
}
{
  "data": {
    "users": [
      { "id": 1, "name": "Neil", "picture": "/avatars/neil.png" },
      { "id": 2, "name": "Buzz", "picture": "/avatars/buzz.png" }
    ]
  }
}

A query is a self-contained slice of the state of your app's data graph, and a mutation is a self-contained action to affect a slice of your graph. They fit naturally into the data-driven web components development model, since a web component is a self-contained unit of HTML UI with programmer-defined behaviours.

Using Apollo Elements, you'll build query components to fetch and display data from the graph and mutation components to make changes to the graph. You can also publish self-contained GraphQL components for others to use in their apps, for example if multiple teams work on the same large dashboard app.

Apollo Elements: Query to UI
query Users {
  users {
    id
    name
    picture
  }
}
  <astro-naut id="1" name="Neil">
    <img src="/avatars/neil.png"
         alt="Portrait of Neil"/>
  </astro-naut>
  <astro-naut id="2" name="Buzz">
    <img src="/avatars/buzz.png"
         alt="Portrait of Buzz"/>
  </astro-naut>

Many Paths to Success

With Apollo Elements, you can write declarative templates and styles for your component in HTML, or you can leverage your favourite web-components library to write your own custom query, mutation, or subscription component. Apollo Elements coordinates between your UI library of choice (or your declarative HTML template) and the Apollo client. Add your query, template, styles, and custom behaviours to Apollo Elements' components, base classes, or helpers functions.

The tabs below demonstrate multiple ways to write the same query component:

<apollo-query>
  <!-- Use a script child like so,
       or set the `query` DOM property on the element -->
  <script type="application/json">
    query Users {
      users {
        id
        name
        picture
      }
    }
  </script>
  <template>
    <h2>Astronauts</h2>
    <template type="repeat" repeat="{{ data.users }}">
      <astro-naut id="{{ item.id }}" name="{{ item.name }}">
        <img src="{{ item.picture }}"
             alt="Portrait of {{ item.name }}"/>
      </astro-naut>
    </template>
  </template>
</apollo-query>
import { ControllerHostMixin } from '@apollo-elements/mixins/controller-host-mixin';
import { ApolloQueryController } from '@apollo-elements/core/apollo-query-controller';

const template = document.createElement("template");
template.innerHTML = `
  <h2>Astronauts</h2>
  <div id="astronauts"></div>
`;

const itemTemplate = document.createElement("template");
itemTemplate.innerHTML = `
  <astro-naut name="">
    <img />
  </astro-naut>
`;

class Astronauts extends ApolloQueryMixin(HTMLElement) {
  query = new ApolloQueryController(this, gql`
    query Users {
      users {
        id
        name
        picture
      }
    }
  `)

  constructor() {
    super();
    this
      .attachShadow({ mode: 'open' })
      .appendChild(template.content.cloneNode(true));
  }

  update() {
    this.render();
    super.update();
  }

  update() {
    const node = this.shadowRoot.getElementById('astronauts');
    for (const child of node.children)
      child.remove();
    for (const { id, name , picture } of this.data?.users ?? []) {
      const astronode = itemTemplate.content.cloneNode(true);
      astronode.id = id;
      astronode.name = name;
      const img = astronode.querySelector('img');
      img.src = picture;
      img.alt = `Portrait of ${name}`;
      node.appendChild(astronode);
    }
  }
}
import { ApolloQueryController } from '@apollo-elements/core/apollo-query-controller';

class Astronauts extends ApolloQuery {
  query = new ApolloQueryController(this, gql`
    query Users {
      users {
        id
        name
        picture
      }
    }
  `);

  render() {
    return html`
      <h2>Astronauts</h2>
      ${(this.query.data?.users ?? []).map(({ id, name, picture }) => html`
      <astro-naut id="${ id }" name="${ name }">
        <img src="${ picture }"
             alt="Portrait of ${ name }"/>
      </astro-naut>
      `)}
    `;
  }
}
import type { TypedDocumentNode } from '@apollo/client/core';
import type { Binding, ViewTemplate } from '@microsoft/fast-element';

import { FASTElement, customElement, html } from '@microsoft/fast-element';
import { ApolloQueryBehavior } from '@apollo-elements/fast';

const getId: Binding<Astronaut> = x => x.id;
const getName: Binding<Astronaut> = x => x.name;
const getPicture: Binding<Astronaut> = x => x.picture;
const getAstronauts: Binding<Astronaut> = x => x.query.data?.users ?? [];
const astronautTemplate: ViewTemplate<Astronaut> = html`
  <astro-naut id="${getId}" name="${getName}">
    <img src="${getPicture}"
         alt="Portrait of ${getName}"/>
  </astro-naut>
`;

const template: ViewTemplate<Astronauts> = html`
  <h2>Astronauts</h2>
  ${repeat(getAstronauts, astronautTemplate)}
`;

const AstronautsQuery: TypedDocumentNode<{ users: { id: string; name: string; picture: string } }> = gql`
  query Users {
    users {
      id
      name
      picture
    }
  }
`;

@customElement({ name: 'astro-nauts', template })
class Astronauts extends FASTElement {
  query = new ApolloQueryBehavior(this, AstronautsQuery);
}
function Astronauts() {
  const { data } = useQuery(gql`
    query Users {
        users {
          id
          name
          picture
        }
      }
  `);

  return html`
    <h2>Astronauts</h2>
    ${(data?.users ?? []).map(({ id, name, picture }) => html`
    <astro-naut id="${ id }" name="${ name }">
      <img src="${ picture }"
           alt="Portrait of ${ name }"/>
    </astro-naut>
    `)}
  `;
}
function Astronauts() {
  const { data } = useQuery(gql`
    query Users {
        users {
          id
          name
          picture
        }
      }
  `);

  return (
    <host shadowDom>
      <h2>Astronauts</h2>
      {(data?.users ?? []).map(({ id, name, picture }) => (
      <astro-naut id={id} name={nam }>
        <img src={picture}
             alt="Portrait of {name}"/>
      </astro-naut>
      ))}
    </host>
  );
}
import { query } from '@apollo-elements/hybrids/factories/query';

define('astro-nauts', {
  query: query(gql`
    query Users {
        users {
          id
          name
          picture
        }
      }
  `),
  render: host => html`
    <h2>Astronauts</h2>
    ${(host.query.data?.users ?? []).map(({ id, name, picture }) => html`
    <astro-naut id="${ id }" name="${ name }">
      <img src="${ picture }"
           alt="Portrait of ${ name }"/>
    </astro-naut>
    `)}
  `,
});

Apollo Elements doesn't lock you in to one way of working. You can build an app's components piecemeal from several different libraries using multiple different paradigms, and they can all consume each other, communicate with each other, and coexist with one another, and couldn't we use some more of that?

Next Steps