Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

TS apps are unable to instantiate ApolloControllers outside of class definition or to specify dynamic ApolloClient instance #274

Open
pospi opened this issue Jan 31, 2023 · 5 comments
Labels
bug Something isn't working

Comments

@pospi
Copy link

pospi commented Jan 31, 2023

Package

@apollo-elements/core

Description

Filing this here to track lit/lit#2446. Once this upstream issue is rectified, this can be marked resolved.

Summary: it should be possible to instantiate ApolloQueryController et. al. within various lifecycle methods (eg. LitElement's connectedCallback). Currently this will cause a runtime error due to the way the TypeScript compiler handles private member access.

Additionally, since ReactiveElement properties are not assigned until after the constructor is called, the standard pattern of passing a client down to child components through element context is not workable (any specified client property will be undefined during class member declarations). This means that ApolloClient is always assigned from the window.__APOLLO_CLIENT__ global and that multiple client instances in the same app are not possible.

Steps To Reproduce

You can see this behaviour in any ReactiveElement which uses a configuration similar to this (example given as a LitElement):

import { createContext, contextProvided } from "@lit-labs/context"
import { property, state } from "lit/decorators.js"
import { LitElement, html } from "lit"
import { ApolloClient, NormalizedCacheObject } from '@apollo/client/core'
import { ApolloQueryController, ApolloController } from '@apollo-elements/core'

const gqlClientContext = createContext<ApolloClient<NormalizedCacheObject>>(
    'graphql-client-context'
);

async function initGraphQLClient() { /* ...initialise & return GraphQL Client object somehow... */ }

// Somewhere high up in the component tree we set a context for the GraphQL Client object
class ContextElement extends LitElement {
  @contextProvider({ context: gqlClientContext })
  @property()
  graphqlClient!: ApolloClient<NormalizedCacheObject>

  connectedCallback() {
    super.connectedCallback()
    initGraphQLClient().then((client) => this.graphqlClient = client)
  }

  render() {
    if (!this.graphqlClient) {
      return html`<div>Connecting...</div>`
    }
    // ...render the application...
  }
}

// Elsewhere within the context we attempt to access the same client
class ReceiverElement extends LitElement {
  @contextProvided({ context: gqlClientContext, subscribe: true })
  @property({ attribute: false })
  client!: ApolloClient<NormalizedCacheObject>

  @state()
  entries?: ApolloController<any>

  connectedCallback() {
    super.connectedCallback()
    // Throws private member access error on this.#hasDisconnected
    // Additionally, this is the only way to assign this.client if passed via properties or context.
    // `this.client` will be undefined and `window.__APOLLO_CLIENT__` will silently override the expected value.
    this.entries = new ApolloQueryController(this, SomeQuery, { client: this.client })
  }

  render() {
    // ...
  }
}

If code similar to the above is compiled with tsc, it will throw a private member access error at the indicated line.

Expected Behavior

We should be able to initialise ApolloController objects and bind them to a host component dynamically at any time in that host component's lifecycle.

Additionally, we should be able to dynamically specify client in the ApolloController options, passing a client which has been assigned via ReactiveElement properties.

@bennypowers
Copy link
Member

Thanks for the issue

There's also <apollo-client> to provide client context in the DOM tree

@pospi
Copy link
Author

pospi commented Feb 3, 2023

There is, but as far as I can tell the behaviour is different. That element (and the ApolloClient it hosts) seems to be located via event propagation mechanisms- see here.

It is not being passed through regular context mechanisms; though (correction) neither is it coming from __APOLLO_CLIENT__, perhaps I saw that in another module. I don't believe client can be set from context, if use of context means that assignment happens after the constructor being called.

@bennypowers
Copy link
Member

@pospi
Copy link
Author

pospi commented Feb 5, 2023

Huh! There you go :) I dug into https://lit.dev/docs/data/context/ a little more and it does seem as though that's the approach they're taking as well.

@bennypowers
Copy link
Member

Yup! Context is a protocol. Any element that follows the protocol can interop with any other, no matter the implementing library

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
bug Something isn't working
Projects
None yet
Development

No branches or pull requests

2 participants