Apollo Elements Apollo Elements Guides API Blog Toggle darkmode

Cool Tricks: Asynchronous Client

In some cases, you may want to wait for your Apollo client to do some initial asynchronous setup (for example reloading a persistent cache or getting a user token) before you can make your client available to your app.

import { ApolloClient, InMemoryCache, HttpLink } from '@apollo/client/core';

const cache = new InMemoryCache();

const link = new HttpLink({
  uri: 'https://api.spacex.land/graphql',
})

let client;

export async function getClient() {
  if (client)
    return client;

  // Wait for the cache to be restored
  await persistCache({ cache, storage: localStorage });

  // Create the Apollo Client
  client =
    new ApolloClient({ cache, link });

  return client;
};

The most straightforward way to do this is first instantiate your client, and only afterwards load your components using dynamic import():

import { getClient } from './client';
(async function init() {
  window.__APOLLO_CLIENT__ = await getClient();
  await Promise.all([
    import('./components/connected-element.js'),
    import('./components/connected-input.js'),
  ]);
})();

If for whatever reason you'd like to load your component files eagerly, set the noAutoSubscribe property on your components, then you can import a promise of a client and wait for it in connectedCallback, calling subscribe when ready.

<apollo-client>
  <apollo-query no-auto-subscribe>
    <template>
      <template type="if" if="{{ data }}">
        <h1>👋 {{ data.userSession.name }}!</h1>
        <p>
          <span>Your last activity was</span>
          <time>{{ getTime(data) }}</time>
        </p>
      </template>
    </template>
  <apollo-query>
</apollo-client>

<script>
  (async function() {
    const { formatDistance } = await import('date-fns/esm');
    const { getClient } = await import('./client');
    const { UserSessionQuery } = await import('./UserSession.query.graphql.js');

    const root = document.currentScript.getRootNode();
    const queryEl = root.querySelector('apollo-query');
    const clientEl = root.querySelector('apollo-client');

    clientEl.client = await getClient();

    queryEl.extras = {
      getTime(data) {
        if (!data || !data.userSession.lastActive)
          return ''
        else {
          return formatDistance(
            data.userSession.lastActive,
            Date.now(), {
              addSuffix: true
          });
        }
      },
    };

    queryEl.query = UserSessionQuery;
    queryEl.subscribe();

  })();
</script>

import { ApolloQueryMixin } from '@apollo-elements/mixins/apollo-query-mixin';

import UserSessionQuery from './UserSession.query.graphql';

import type {
  UserSessionQueryData as Data,
  UserSessionQueryVariables as Variables,
} from '../schema';

import { getClient } from './client';
import { formatDistance } from 'date-fns/esm';

const template = document.createElement('template');
template.innerHTML = `
  <h1>👋 </h1>
  <p>
    <span>Your last activity was</span>
    <time></time>
  </p>
`;

template.content.querySelector('h1').append(new Text(''));
template.content.querySelector('h1').append(new Text('!'));
template.content.querySelector('time').append(new Text(''));

class AsyncElement extends ApolloQueryMixin(HTMLElement)<Data, Variables> {
  noAutoSubscribe = true;

  query = UserSessionQuery;

  get data() {
    return this.#data;
  }

  set data(value: Data) {
    this.#data = value;
    this.render();
  }

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

  async connectedCallback() {
    super.connectedCallback();
    // asynchronously get a reference to the client
    this.client = await getClient();
    // only then start fetching data
    this.subscribe();
  }

  render() {
    const lastActive = this.data?.userSession.lastActive;

    const [waveNode, nameNode] =
      this.shadowRoot.querySelector('h1').childNodes;
    const [timeNode] =
      this.shadowRoot.querySelector('time').childNodes;

    nameNode.data = this.data?.userSession.name ?? '';
    timeNode.data =
      !lastActive ? '' : formatDistance(lastActive, Date.now(), { addSuffix: true });
  }
};

customElements.define('async-element', AsyncElement);
import { ApolloQueryController } from '@apollo-elements/core';
import { LitElement, html } from 'lit';
import { customElement } from 'lit/decorators.js';
import { UserSessionQuery } from './UserSession.query.graphql';
import { getClient } from './client';
import { formatDistance } from 'date-fns/esm';

@customElement('async-element')
class AsyncElement extends LitElement {
  query = new ApolloQueryController(this, UserSessionQuery);

  async connectedCallback() {
    super.connectedCallback();
    // asynchronously get a reference to the client
    // setting the client will automatically subscribe.
    this.query.client = await getClient();
  }

  render() {
    const name = this.query.data?.userSession?.name ?? '';
    const lastActive = this.query.data?.userSession?.lastActive;

    const time =
      !lastActive ? '' : formatDistance(lastActive, Date.now(), { addSuffix: true });

    return html`
      <h1>👋 ${name}!</h1>
      <p>
        <span>Your last activity was</span>
        <time>${time}</time>
      </p>
    `;
   }
};
import { FASTElement, customElement, html, ViewTemplate } from '@microsoft/fast-element';
import { ApolloQueryBehavior } from '@apollo-elements/fast';

import { UserSessionQuery } from './UserSession.query.graphql';

import { getClient } from './client';
import { formatDistance } from 'date-fns/esm';

function getTime(userSession): string {
  const lastActive = userSession?.lastActive;
  return (
      !lastActive ? ''
    : formatDistance(new Date(lastActive), Date.now(), { addSuffix: true })
  );
}

const template: ViewTemplate<AsyncElement> = html`
  <h1>👋 ${x => x.data?.userSession.name}!</h1>
  <p>
    <span>Your last activity was</span>
    <time>${x => getTime(x.data?.userSession)}</time>
  </p>
`;

@customElement({ name: 'async-element', template })
class AsyncElement extends FASTElement {
  noAutoSubscribe = true;

  query = new ApolloQueryBehavior(this, UserSessionQuery);

  async connectedCallback() {
    super.connectedCallback();
    // asynchronously get a reference to the client
    // setting the client will start fetching the query
    this.query.client = await getClient();
  }
};
import { useQuery, component, html } from '@apollo-elements/haunted';

import { UserSessionQuery } from './UserSession.query.graphql';

import { getClient } from './client';
import { formatDistance } from 'date-fns/esm';

function AsyncElement(el) {
  const query =
    useQuery(UserSessionQuery, { noAutoSubscribe: true });

  useEffect(async () => {
    // asynchronously get a reference to the client
    query.client = await getClient();
    // only then start fetching data
    query.subscribe();
  }, []);

  const name = query.data?.userSession.name ?? ''
  const lastActive = query.data?.userSession.lastActive;

  const time =
    !lastActive ? '' : formatDistance(lastActive, Date.now(), { addSuffix: true });

  return html`
    <h1>👋 ${name}!</h1>
    <p>
      <span>Your last activity was</span>
      <time>${time}</time>
    </p>
  `;
};

customElements.define('async-element', component(AsyncElement));
import { useQuery, c } from '@apollo-elements/atomico';

import { UserSessionQuery } from './UserSession.query.graphql';

import { getClient } from './client';
import { formatDistance } from 'date-fns/esm';

function AsyncElement() {
  const query =
    useQuery(UserSessionQuery, { noAutoSubscribe: true });

  useEffect(async () => {
    // asynchronously get a reference to the client
    query.client = await getClient();
    // only then start fetching data
    query.subscribe();
  }, []);

  const name = query.data?.userSession.name ?? ''
  const lastActive = query.data?.userSession.lastActive;

  const time =
    !lastActive ? '' : formatDistance(lastActive, Date.now(), { addSuffix: true });

  return (
    <host shadowDom>
      <h1>👋 {name}!</h1>
      <p>
        <span>Your last activity was</span>
        <time>{time}</time>
      </p>
    </host>
  );
};

customElements.define('async-element', c(AsyncElement));
import { query, define, property, html } from '@apollo-elements/hybrids';

import { getClient } from './client';
import { formatDistance } from 'date-fns/esm';

import { UserSessionQuery } from './UserSession.query.graphql';

function getTime(userSession): string {
  const lastActive = userSession?.lastActive;
  return (
      !lastActive ? ''
    : formatDistance(new Date(lastActive), Date.now(), { addSuffix: true })
  );
}

async function connect(host) {
  // asynchronously get a reference to the client.
  // setting the client will automatically start querying.
  host.query.client = await getClient();
}

define('async-element', {
  // use 'connect' to gain access to connectedCallback
  __asyncClient: { connect },
  query: query(UserSessionQuery),
  render: ({ query }) => html`
    <h1>👋 ${query.data?.userSession.name}!</h1>
    <p>
      <span>Your last activity was</span>
      <time>${getTime(query.data?.userSession)}</time>
    </p>
  `
})