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

Design #2

Closed
FluorescentHallucinogen opened this issue May 28, 2018 · 17 comments
Closed

Design #2

FluorescentHallucinogen opened this issue May 28, 2018 · 17 comments

Comments

@FluorescentHallucinogen
Copy link
Contributor

If I understand correctly, mutations and subscriptions in GraphQL are also queries (special queries).

So what about the only single one universal web component instead of 3 (<apollo-query>, <apollo-mutation>, <apollo-subscription>)?

@bennypowers
Copy link
Member

The distal explanation is that Mutations are a special case because of things like the update and optimisticResponse updates.
The proximal explanation is that I lifted the design from react-apollo 😉

However, in our library, we're not providing web-components per se, instead we're making base classes which authors are meant to extend, so whereas a polymer implementation of apollo would look like:

<apollo-query query="[[query]]" data="{{data}}"></apollo-query>
<my-view data="[[data]]"></my-view>

Our library extends LitElement to make it easier to integrate your apollo-client into your elements:

<script type="module">
  import gql from 'graphql-tag'
  import { ApolloQuery, ApolloMutation, html } from 'lit-apollo';

  customElements.define('view-element', class ViewElement extends ApolloQuery {
    _render({data}) {
      return html`
        <h1>value is ${data.value}</h1>
        <mutate-input></mutate-input>
      `;
      }
  });

  customElements.define('mutate-input', class MutateInput extends ApolloMutation {
    _render({ value }) {
      return html`
        <input
          value="${value}"
          on-input="${e => this.mutate({ variables: { value: e.target.value } })}">`;
    }

    constructor() {
      super();
      this.mutation = gql`mutation($input: String) { updateInput(input: $input) { value }`;
    }
  });
</script>

<view-element>
  <!-- this sets the `query` property on view-element -->
  <script type="application/graphql">query { input { value } }</script>
</view-element>

@FluorescentHallucinogen
Copy link
Contributor Author

@bennypowers Are there any plans to implement web component(s) (on top of your base classes)?

Using lit-element doesn't mean you can't create web components like using Polymer. You can just use LitElement instead of PolymerElement as base class.

Material Web Components are good example of set of web components built on top of LitElement.

Your current implementation looks very much like https://github.com/aruntk/polymer-apollo.

But I like more https://webcomponents.org/element/reach-digital/polymer-apollo-client. The only downside is that it doesn't support GraphQL real-time subscriptions (updates).

Please look at aruntk/polymer-apollo#21 and my big comments FirebaseExtended/polymerfire#274 (comment) and FirebaseExtended/polymerfire#274 (comment) to see why declarative web components better than mixins / base classes.

What do you think about this?

@bennypowers
Copy link
Member

Can you show me an example of a document you'd like to build with these elements? How would It look, what would the APIs be like? I want to get a better idea of your use case.

@FluorescentHallucinogen
Copy link
Contributor Author

Since web components are interoperable, they can be used in a Polymer project, despite the fact that they themselves are written using lit-element.

Something like:

If I use apollo web components in <my-view> written using Polymer:

import {PolymerElement, html} from '@polymer/polymer/polymer-element.js';
import '@polymer/paper-input/paper-input.js';
// import apollo web components

class MyView extends PolymerElement {

  static get properties() {
    return {
      searchId: {
        type: String
      },
      data: {
        type: Object
      }
    }
  }

  static get template() {
    return html`
      <paper-input value="{{searchId}}"></paper-input>

      <apollo-config uri="https://graphql.endpoint/graphql"></apollo-config>

      <apollo-client
          query='{
            post(id: "[[searchId]]") {
              id
              title
              content
              published
            }
          }'
          data="{{data}}">
      </apollo-client>

      Post title: [[data.post.title]]
      Post content: [[data.post.content]]
    `;
  }

}

customElements.define('my-view', MyView);

If I use apollo web components in <my-view> written using lit-element (there is no two-way data binding in lit-element):

import {LitElement, html} from '@polymer/lit-element/lit-element.js';
import '@polymer/paper-input/paper-input.js';
// import apollo web components

export class MyView extends LitElement {

  static get properties() {
    return {
      searchId: String,
      data: Object
    }
  }

  onValueChanged(e) {
    this.searchId = e.detail.value;
  }

  onDataChanged(e) {
    this.data = e.detail.value;
  }

  _render({searchId, data}) {
    return html`
      <paper-input on-value-changed="${this.onValueChanged.bind(this)}"></paper-input>

      <apollo-config uri="https://graphql.endpoint/graphql"></apollo-config>

      <apollo-client
          query='{
            post(id: "${searchId}") {
              id
              title
              content
              published
            }
          }'
          on-data-changed="${this.onDataChanged.bind(this)}">
      </apollo-client>

      Post title: ${data.post.title}
      Post content: ${data.post.content}
    `;
  }

}

customElements.define('my-view', MyView);

GraphQL varibles

When the searchId variable change, the query gets automatically re-fetched.
We can dinamically change searchId variable value using Polymer data binding.

GraphQL real-time subscriptions (updates)

<apollo-client
    subscription='{
      post(published: true) {
        id
        title
        content
        published
      }
    }'
    data="{{data}}">
</apollo-client>

When published posts (posts with published: true) change (added or deleted), data automatically changes.

Desing questions

First variant — one single universal <apollo-client> web component for queries, mutations and subscriptions (query, mutation, subscription are attributes (properties) of the web component):

<apollo-client
    query=""
    data="">
</apollo-client>

<apollo-client
    mutation=""
    data="">
</apollo-client>

<apollo-client
    subscription=""
    data="">
</apollo-client>

Second variant — one single universal <apollo-client> web component for queries, mutations and subscriptions (query, mutation, subscription are keywords that used inside query):

<apollo-client
    query='
      query {
        users {
          id
          name
          email
          isAdmin
        }
      }'
    data="">
</apollo-client>

<apollo-client
    query='
      mutation {
        createUser(data: {
          name: "Alice"
          email: "alice@prisma.io"
        }) {
          id
        }
      }'
    data="">
</apollo-client>

<apollo-client
    query='
      subscription {
        post(published: true) {
          id
          title
          content
          published
        }
      }'
    data="">
</apollo-client>

@FluorescentHallucinogen
Copy link
Contributor Author

Variables in GraphQL uses $name syntax: http://facebook.github.io/graphql/October2016/#sec-Language.Variables

Is it possible to use (Polymer) data binding and GraphQL $name syntax (not ${name}, not [[name]]) in queries (automatically associate name property value with $name GraphQL variable value?

@FluorescentHallucinogen
Copy link
Contributor Author

Looks like it is possible to write queries in the body of web component, not in query property. Something like:

<apollo-client result="{{data}}">
  query Pagination($skip: Int!) {
    allStarships(first: 5, skip: $skip) {
      name
      class
    }
  }
</apollo-client>

or

<apollo-client result="{{data}}">
  <script type="application/graphql">
    query {
      allStarships(first: 5, filter: {pilots_some: {name_not: ""}}) {
        name
        class
        pilots {
          name
          homeworld {
            name
          }
        }
      }
    }
  </script>
</apollo-client>

What is better?

Is it possible to use varibles if the query placed in the body of web component (not in query property)? Is it possible to use Polymer data binding in this case to dinamically change values of variables? Something like:

static get template() {
  return html`
    <apollo-client data="{{data}}">
      query {
        post(id: [[searchId]]) {
          id
          title
          content
          published
        }
      }
    </apollo-client>
  `;
}

or

_render({searchId}) {
  return html`
    <apollo-client on-data-changed="${this.onDataChanged.bind(this)}">
      query {
        post(id: ${searchId}) {
          id
          title
          content
          published
        }
      }
    </apollo-client>
  `;
}

Will it work inside <script type="application/graphql">? Something like:

static get template() {
  return html`
    <apollo-client data="{{data}}">
      <script type="application/graphql">
        query {
          post(id: [[searchId]]) {
            id
            title
            content
            published
          }
        }
      </script>
    </apollo-client>
  `;
}

or

_render({searchId}) {
  return html`
    <apollo-client on-data-changed="${this.onDataChanged.bind(this)}">
      <script type="application/graphql">
        query {
          post(id: ${searchId}) {
            id
            title
            content
            published
          }
        }
      </script>
    </apollo-client>
  `;
}

@FluorescentHallucinogen
Copy link
Contributor Author

Should I escape " symbols inside the query? Something like:

<apollo-client
  query="{ repository(owner: \"bennypowers\", name:\"lit-apollo\") { url } }"
  last-response="{{response}}">
</apollo-client>

Seems if I use ', I shouldn't?

<apollo-client
  query='{ repository(owner: "bennypowers", name: "lit-apollo") { url } }'
  last-response="{{response}}">
</apollo-client>

@FluorescentHallucinogen
Copy link
Contributor Author

Should I wrap string variables in double quotes?

I.e.

<apollo-client
    query='{
      post(id: "[[searchId]]") {
        id
        title
      }
    }'
    data="{{data}}">
</apollo-client>

insted of

<apollo-client
    query='{
      post(id: [[searchId]]) {
        id
        title
      }
    }'
    data="{{data}}">
</apollo-client>

or

<apollo-client
    query='{
      post(id: "${searchId}") {
        id
        title
      }
    }'
    on-data-changed="${this.onDataChanged.bind(this)}">
</apollo-client>

instead of

<apollo-client
    query='{
      post(id: ${searchId}) {
        id
        title
      }
    }'
    on-data-changed="${this.onDataChanged.bind(this)}">
</apollo-client>

@bennypowers
Copy link
Member

bennypowers commented Jun 1, 2018

First of all thank you for the detailed descriptions.

There are a few things I think we need to consider here:

  1. The polymer templating system, though not deprecated, will not be developed in the future. As such, view components that alter their own data are not preferred, rather, events should be exposed to some central state container. This isn't the only design out there, but it is by a large margin the most popular.
  2. I reached out to the apollo client developers, and they have no plans yet to support apps that do not use a bundler. Until they come around, I think it would be misleading to insinuate that we can provide drop-in, work-anywhere components based on apollo-client. The ticket price is a commonjs bundler.
  3. To support legacy polymer3 apps, it should be simple enough to:
customElements.define("apollo-query", class ApolloQueryElement extends ApolloQuery {
 fire(type, detail) {
   this.dispatchEvent(new CustomEvent(type, {
     bubbles: true,
     composed: true,
     detail,
   }
 }

 _propertiesChanged(props, changedProps, oldProps) {
    super._propertiesChanged(props, changedProps, oldProps);
    const {data, error, loading, networkStatus} = changedProps;
    (data) && this.fire("data-changed", { value: data });
    (error) && this.fire("error-changed", { value: error });
    (loading) && this.fire("loading-changed", { value: loading });
    (networkStatus) && this.fire("network-status-changed", { value: networkStatus });
 }
}) 

That would provide a similar API to the currently available polymer2 Apollo components. I'd be happy to provide such a component under lit-apollo/legacy/apollo-query.js, likewise for mutation.

Would that, and perhaps some guidance around bundling in the README, be enough to cover your case?

@FluorescentHallucinogen
Copy link
Contributor Author

I'd be happy to provide such a component under lit-apollo/legacy/apollo-query.js, likewise for mutation.

It would be very great.

I reached out to the apollo client developers, and they have no plans yet to support apps that do not use a bundler. Until they come around, I think it would be misleading to insinuate that we can provide drop-in, work-anywhere components based on apollo-client. The ticket price is a commonjs bundler.

Look like it's possible to bundle only apollo-client (not the whole app). See e.g.:

https://github.com/matteo-hertel/polymer-graphql/blob/master/polymer/libs/apollo-client.html

https://github.com/reach-digital/polymer-apollo-client/blob/master/build/apollo-client.js

@bennypowers
Copy link
Member

It's not that simple if you want to use custom authentication, subscription transport, etc. I think the apollo developers are set in their ways about requiring (and encouraging) a build step, and until that changes, I'd rather not let on that it's easy to just plug in some pre-made components and get a fully-functional apollo client

@FluorescentHallucinogen
Copy link
Contributor Author

@unicodeveloper PTAL, need your help.

@FluorescentHallucinogen
Copy link
Contributor Author

@bennypowers

in our library, we're not providing web-components per se, instead we're making base classes which authors are meant to extend

It seems that apollo-mutation is in fact a web component (custom element): https://github.com/bennypowers/lit-apollo/blob/master/apollo-mutation.js#L106, but apollo-query is not. Why?

@bennypowers
Copy link
Member

typo :)

Slightly more legit answer: The design for ApolloMutation is still a WIP. I'm not amazingly happy with the way it is now - you may have to define a new component for each type of mutation. So if your app has 20 kinds of text property mutations, that could potentially be 20 different mutations.

@bennypowers
Copy link
Member

Version 0.1.0 updates the readme to include the example p3-compatible component. Is that enough to close this issue?

@bennypowers
Copy link
Member

bennypowers commented Jul 23, 2018

I’ve been considering making <apollo-mutation> a decorator element, so you could

<apollo-mutation mutation="${mutation}" mutate-on="input">
  <input slot="mutator"/>
</apollo-mutation>

where mutate-on accepts an event name, which it would listen for on the element in the mutator slot.

I think the pros to this approach would be DX - not having to create a new one-off class with its own _render method for each mutating element.

What would be the implications for styling/dom?

@bennypowers
Copy link
Member

Closing in favour of #11

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

2 participants