当前位置: 首页 > 软件库 > 大数据 > 数据查询 >

ember-apollo-client

授权协议 MIT License
开发语言 Java
所属分类 大数据、 数据查询
软件类型 开源软件
地区 不详
投 递 者 祝俊雄
操作系统 跨平台
开源组织
适用人群 未知
 软件概览

ember-apollo-client

Use @apollo/client and GraphQL from your Ember app.

Build Status

This addon is battle tested: it has been used to build several large apps. As such, we've solved real-world problems such as reliable testing and preventing resource leaks by unsubscribing from watch queries.

Installation

ember install ember-apollo-client

This should also automatically install ember-fetch and graphql.

Install the Apollo Client Developer tools for Chrome for a great GraphQL developer experience!

Compatibility

  • Apollo Client v3.0 or above
  • Ember.js v3.12 or above
  • Ember CLI v2.13 or above
  • Node.js v10 or above
  • FastBoot 1.0+

For compatibility with Ember versions below 3.4, use version 1.x.For compatibility with Apollo Client v1 or v2, use version 1.x or 2.x of this addon.

Configuration

Runtime configuration

In your app's config/environment.js, configure the URL for the GraphQL API.

let ENV = {
  ...
  apollo: {
    apiURL: 'https://test.example/graphql',
    // Optionally, set the credentials property of the Fetch Request interface
    // to control when a cookie is sent:
    // requestCredentials: 'same-origin', // other choices: 'include', 'omit'
  },
  ...
}

Additional configuration of the ApolloClient can be done by extending the Apolloservice and overriding the clientOptions property. See theApollo Service API for more info.

Build time configuration

In your app's ember-cli-build.js, you can set build time options for broccoli-graphql-filter to keep or remove file extensions in .graphql files.

module.exports = function(defaults) {
  let app = new EmberApp(defaults, {
    emberApolloClient: {
      keepGraphqlFileExtension: false
    }
  });

  return app.toTree();
};

keepGraphqlFileExtension = true, defaults to true – If false, creates files called my-query.js instead of my-query.graphql.js, so that you can import them as ./my-query instead of ./my-query.graphql.

Example:

import myQuery from 'my-app/queries/my-query.graphql';

Dependencies

This addon uses ember-auto-import to import dependencies.

This addon does not exposes any dependencies directly to your application, soif you desire any additional graphql or apollo dependencies, install them withnpm/yarn and import as desired.

Here are some useful packages:

Make sure to use ember-auto-import in your application to import theseadditional packages.

Peer Dependencies

This addon has a peer dependency of:

Usage

Fetching data

GraphQL queries should be placed in external files, which are automaticallymade available for import:

app/gql/queries/human.graphql

query human($id: String!) {
  human(id: $id) {
    name
  }
}

You can also use the graphql-tag package to write your queries within yourJS file:

import gql from "graphql-tag";

const query = gql`
  query human($id: String!) {
    human(id: $id) {
      name
    }
  }
`;

Note: Inline queries like the one above are compiled at runtime. This isboth slower than external files (which are precompiled) and involves shippingextra dependencies in your vendor.js. For the time being, we recommend usingexternal files for your queries.

If you are looking for an opportunity to contribute, enabling precompilationof inline queries would be a fantastic feature to work on.

Within your routes, you can query for data using the queryManagercomputed macro and watchQuery:

app/routes/some-route.js

import Route from "@ember/routing/route";
import { queryManager } from "ember-apollo-client";
import query from "my-app/gql/queries/human.graphql";

export default Route.extend({
  apollo: queryManager(),

  model(params) {
    let variables = { id: params.id };
    return this.apollo.watchQuery({ query, variables }, "human");
  }
});

This performs a watchQuery on the ApolloClient. The resulting object is a POJO.

If a subsequent query (such as a mutation) happens to fetch the same data whilethis query's subscription is still active, the object will immediately receivethe latest attributes (just like ember-data).

Please note that when using watchQuery, you mustunsubscribe when you're done with the query data. You shouldonly have to worry about this if you're using the Apolloservice directly. If you use the queryManager computed macro in your routes, or in your data-loadingcomponents or class that extend Ember.Object, all active watch queries are tracked and unsubscribed when the route is exited or the component and Ember.Object is destroyed.

You can instead use query if you just want a single query with a POJOresponse and no watch updates.

If you need to access the Apollo Client ObservableQuery,such as for pagination, you can retrieve it from a watchQuery result usinggetObservable:

import Route from "@ember/routing/route";
import { getObservable, queryManager} from "ember-apollo-client";

export default Route.extend({
  apollo: queryManager(),

  model() {
    let result = this.apollo.watchQuery(...);
    let observable = getObservable(result);
    observable.fetchMore(...) // utilize the ObservableQuery
    ...
  }
});

See the detailed query manager docs for more details onusage, or the Apollo service API if you need to usethe service directly.

GraphQL Subscriptions

GQL Subscriptions allow a client to subscribe to specific queries they are interested in tracking. The syntax for doing this is similar to query / watchQuery, but there are a few main differences:

  • you must define a subscription (versus a query or mutation)
  • because subscriptions are async by nature, you have to listen for these events and act accordingly.
  • subscriptions require websockets, so must configure your link accordingly

Creating your subscription

app/gql/subscriptions/new-human.graphql

subscription {
  newHuman() {
    name
  }
}

Subscribing from inside a route

app/routes/some-route.js

import Route from '@ember/routing/route';
import { queryManager } from 'ember-apollo-client';
import query from 'app/gql/subscriptions/new-human';
import { addListener, removeListener } from '@ember/object/events';

const handleEvent = event => alert(`${event.name} was just born!`);

export default Route.extend({
  apollo: queryManager(),

  model() {
    return this.get('apollo').subscribe({ query }, 'human');
  },

  setupController(controller, model) {
    addListener(model, 'event', handleEvent);
  },

  resetController(controller, isExiting, transition) {
    if (isExiting) {
      removeListener(controller.model, 'event', handleEvent);
    }
  }
});

The big advantage of using the queryManager is that when you navigate away from this route, all subscriptions created will be terminated. That said, if you want to manually unsubscribe (or are not using the queryManager) subscription.unsubscribe() will do the trick.

Enabling Websockets

While this library should work w/ any back-end implementation, here's an example with Authenticated Phoenix + Absinthe:

my-app/services/apollo.js

import ApolloService from 'ember-apollo-client/services/apollo';
import { inject as service } from '@ember/service';
import { Socket } from 'phoenix';
import { createAbsintheSocketLink } from '@absinthe/socket-apollo-link';
import AbsintheSocket from '@absinthe/socket';

class OverriddenApollo extends ApolloService {
  @service
  session;

  link() {
    const socket = new Socket("ws://socket-url", {
      params: { token: this.get('session.token') },
    });
    const absintheSocket = AbsintheSocket.create(socket);

    return createAbsintheSocketLink(absintheSocket);
  }
}

Note: This will switch all gql communication to use websockets versus http.If you want to conditionally use websockets for only subscriptions (a common pattern)this is where Apollo Link Composition comes in.Specifically, the split function is what we're after (note we are usingapollo-utilities, a helpful npm package):

my-app/services/apollo.js

import ApolloService from 'ember-apollo-client/services/apollo';
import { inject as service } from '@ember/service';
import { Socket } from 'phoenix';
import { split } from '@apollo/client';
import { getMainDefinition } from '@apollo/client/utilities';
import { createAbsintheSocketLink } from '@absinthe/socket-apollo-link';
import AbsintheSocket from '@absinthe/socket';

class OverriddenApollo extends ApolloService {
  @service
  session;

  link() {
    let httpLink = super.link();

    const socket = new Socket("ws://socket-url", {
      params: { token: this.get('session.token') },
    });
    const socketLink = createAbsintheSocketLink(AbsintheSocket.create(socket));

    return split(
      ({ query }) => {
        const { kind, operation } = getMainDefinition(query);

        return kind === 'OperationDefinition' && operation === 'subscription';
      },
      socketLink,
      httpLink
    );
  }
}

Note: You will need to add the following dependencies to your project:

yarn add -D @apollo/client
yarn add -D @absinthe/socket
yarn add -D @absinthe/socket-apollo-link

Mutations and Fragments

You can perform a mutation using the mutate method. You can also use GraphQLfragments in your queries. This is especially useful if you want to ensure thatyou refetch the same attributes in a subsequent query or mutation involving thesame model(s).

The following example shows both mutations and fragments in action:

app/gql/fragments/review-fragment.graphql

fragment ReviewFragment on Human {
  stars
  commentary
}

app/gql/mutations/create-review.graphql

#import ReviewFragment from 'my-app/gql/fragments/review-fragment.graphql'

mutation createReview($ep: Episode!, $review: ReviewInput!) {
  createReview(episode: $ep, review: $review) {
    review {
      ...ReviewFragment
    }
  }
}

app/routes/my-route.js

import Route from "@ember/routing/route";
import { inject as service } from "@ember/service";
import mutation from "my-app/gql/mutations/create-review.graphql";

export default Route.extend({
  apollo: service(),

  actions: {
    createReview(ep, review) {
      let variables = { ep, review };
      return this.get("apollo").mutate({ mutation, variables }, "review");
    }
  }
});

Query manager API

  • watchQuery(options, resultKey): This calls theApolloClient.watchQuery method. It returns a promise thatresolves with a POJO. That object will be updated whenever thewatchQuery subscription resolves with new data. As before, the resultKeycan be used to resolve beneath the root.

    The query manager will automatically unsubscribe from this object.

  • subscribe(options, resultKey): This calls theApolloClient.subscribe method. It returns a promise thatresolves with an EmberApolloSubscription. You can use this object in a few ways to keeptrack of your subscription:

    • emberApolloSubscription.lastEvent; // return the most recently received event data
    //import { addListener, removeListener } from '@ember/object/events';
    
    const result = await this.apollo.subscribe(
      {
        subscription: mySubscription,
      }
    );
    
    const handleEvent = event => {
      console.log('event received', event)
    };
    
    // Add listener to new data
    addListener(result, 'event', handleEvent);
    
    // Remove the listener from new data
    removeListener(result, 'event', handleEvent);

    As before, the resultKey can be used to resolve beneath the root.

    The query manager will automatically unsubscribe from this object. If you want to manuallyunsubscribe, you can do so with emberApolloSubscription.apolloUnsubscribe();

  • query(options, resultKey): This calls theApolloClient.querymethod. It returns a promise that resolves with the raw POJO data that thequery returns. If you provide a resultKey, the resolved data is grabbed fromthat key in the result.

  • mutate(options, resultKey): This calls theApolloClient.mutatemethod. It returns a promise that resolves with the raw POJO data that themutation returns. As with the query methods, the resultKey can be used toresolve beneath the root.

Apollo service API

You should not need to use the Apollo service directly for most regularusage, instead utilizing the queryManager computed macro. However, you will probably need to customize options on the apollo service, and might need to query it directly for some use cases (such asloading data from a service rather than a route or component).

The apollo service has the following public API:

  • clientOptions: This function should return the options hash thatwill be passed to the ApolloClient constructor. You canoverride this function to configure the client this service uses:

    class OverriddenApolloService extends ApolloService {
      clientOptions() {
        return {
          link: this.link(),
          cache: this.cache(),
        };
      }
    }
  • link: This function provides a list of middlewares and afterwares to the Apollo Link the interface for fetching and modifying control flow of GraphQL requests. To create your middlewares/afterwares:

    link() {
        let httpLink = super.link()
    
        // Middleware
        let authMiddleware = setContext(async(request, context) => {
          if (!token) {
            token = await localStorage.getItem('token') || null;
          }
    
          Object.assign(context.headers, {
            headers: {
              authorization: token
            }
          });
    
          return context;
        });
    
        // Afterware
        const resetToken = onError(({ networkError }) => {
          if (networkError && networkError.statusCode === 401) {
            // remove cached token on 401 from the server
            token = undefined;
          }
        });
    
        const authFlowLink = authMiddleware.concat(resetToken);
    
        return authFlowLink.concat(httpLink);
      }

    Example with ember-simple-auth:

    import ApolloService from 'ember-apollo-client/services/apollo';
    import { inject as service } from '@ember/service';
    import { setContext } from '@apollo/client/link/context';
    import { Promise } from 'rsvp';
    
    class OverriddenApollo extends ApolloService {
      @service
      session;
    
      link() {
        let httpLink = super.link();
    
        let authLink = setContext((request, context) => {
          return this._runAuthorize(request, context);
        });
        return authLink.concat(httpLink);
      }
    
      _runAuthorize() {
        if (!this.get('session.isAuthenticated')) {
          return {};
        }
        return new Promise(success => {
          let headers = {};
          let token = this.get('session.data.authenticated.token');
          headers['Authorization'] = `Bearer ${token}`;
    
          success({ headers });
        });
      }
    }

    Note: You will need to add the following dependencies to your project:

    yarn add -D @apollo/client
  • watchQuery(options, resultKey): This calls theApolloClient.watchQuery method. It returns a promise thatresolves with a POJO. That object will be updated whenever thewatchQuery subscription resolves with new data. As before, theresultKey can be used to resolve beneath the root.

    When using this method, it is important to unsubscribefrom the query when you're done with it.

  • query(options, resultKey): This calls theApolloClient.querymethod. It returns a promise that resolves with the raw POJO data that thequery returns. If you provide a resultKey, the resolved data is grabbed fromthat key in the result.

  • mutate(options, resultKey): This calls theApolloClient.mutatemethod. It returns a promise that resolves with the raw POJO data that themutation returns. As with the query methods, the resultKey can be used toresolve beneath the root.

Unsubscribing from watch queries

Apollo Client's watchQuery will continue to update the query with new datawhenever the store is updated with new data about the resolved objects. Thishappens until you explicitly unsubscribe from it.

In ember-apollo-client, most unsubscriptions are handled automatically by thequeryManager computed macro, so long as you use it.

If you're fetching data elsewhere, such as in an Ember Service, or if you usethe Apollo service directly, you are responsible for unsubscribing fromwatchQuery results when you're done with them, you can use unsubscribe:

import Service from "@ember/service";
import { unsubscribe } from "ember-apollo-client";
import { inject as service } from '@ember/service';

export default Service.extend( {
  apollo: service(),
  result: null,

  init() {
    this._super(...arguments);
    this.result = this.apollo.watchQuery(...);
  },

  willDestroy() {
    unsubscribe(this.result)
  }
});

queryManager as decorator

The queryManager computed macro can be used as a decorator when using Ember v3.10.0 or above.

import Route from '@ember/routing/route';
import { queryManager } from 'ember-apollo-client'
import query from 'my-app/gql/queries/human.graphql';

export default class MyAwesomeRoute extends Route {
  @queryManager apollo;

  model({ id }) {
    let variables = { id };
    return this.apollo.watchQuery({ query, variables });
  }
}

queryManager options

The queryManager computed macro can accept an options hash with the name of the service to use as apollo.If your application has a custom apollo service or multiple apollo services that extends from ember-apollo-client/services/apollo, you can use this option to specify which apollo service to use.

// imports ...

export default class MyAwesomeRoute extends Route {
  @queryManager({ service: 'my-custom-apollo-service' }) apollo;

  // ...
}

Use with Fastboot

Ember Apollo Client works with FastBoot out of the box as long that SSR is enabled. In order to enable SSR, define it on apollo service:

Example:

class OverriddenApolloService extends ApolloService {
  clientOptions() {
    const opts = super.clientOptions();
    return {...opts, ssrMode: true };
  }
}

Since you only want to fetch each query result once, pass the ssrMode: true option to the Apollo Client constructor to avoid repeated force-fetching.

Skipping queries for SSR

If you want to intentionally skip a query during SSR, you can pass ssr: false in the query options. Typically, this will mean the component will get rendered in its loading state on the server. For example:

actions: {
  refetchModel() {
    this.get('apollo').query({
      query,
      variables,
      // Don't run this query on the server
      ssr: false
    });
  }
}

Using With TypeScript

When using TypeScript (with ember-cli-typescript in your Ember app) you will quickly run into an error like:

Cannot find module './users.graphql'.

This error happens when you import a *.graphql file, e.g. import query from './users.graphql';.The quick solution is to use // @ts-ignore, but that is only a patch for the one place you've used the import.

To define basic types for those imports, you need to add the following to types/global.d.ts:

// Apollo GraphQL imports
declare module '*.graphql' {
  const doc: import('graphql').DocumentNode;
  export default doc;
}

Note: The graphql module above is included when you ember install ember-apollo-client.

Testing

This addon is test-ready! All promises from the apollo service are tracked withEmber.Test.registerWaiter, so your tests should be completely deterministic.

The dummy app contains example routes for mutations and queries:

The tests also contain a sample Star Wars GraphQL schema with anpretender setup for mock data.

Development

Installation

  • git clone https://github.com/ember-graphql/ember-apollo-client this repository
  • cd ember-apollo-client
  • yarn install

Linting

  • yarn run lint:hbs
  • yarn run lint:js
  • yarn run lint:js --fix

Running tests

  • ember test – Runs the test suite on the current Ember version
  • ember test --server – Runs the test suite in "watch mode"
  • ember try:each – Runs the test suite against multiple Ember versions

Running the dummy application

For more information on using ember-cli, visit https://ember-cli.com/.

Contributors

A special thanks to the following contributors:

Contributing

See the Contributing guide for details.

License

This project is licensed under the MIT License.

 相关资料
  • Apollo Client 是一个全功能的 GraphQL 客户端,用于 React 、Angular 的交互。允许你轻松通过 GraphQL 获取数据并构建 UI 组件。

  • Apollo(阿波罗)是携程框架部门研发的分布式配置中心,能够集中化管理应用不同环境、不同集群的配置,配置修改后能够实时推送到应用端,并且具备规范的权限、流程治理等特性,适用于微服务配置管理场景。 Swoft 基于 Apollo 提供的 API,在之上进行封装,使之能在 Swoft 中快速使用。 安装 swoft/whoops 作为一个额外的扩展组件,需要手动安装: Composer 安装 com

  • Apollo以ActiveMQ原型为基础,是一个更快、更可靠、更易于维护的消息代理工具。Apache称Apollo为最快、最强健的 STOMP(Streaming Text Orientated Message Protocol,流文本定向消息协议)服务器。 Apollo的特性如下: 支持Stomp 1.0和Stomp 1.1协议 主题和队列 队列浏览器 主题持久订阅 镜像队列 可靠的消息传递 消

  • Apollo Client Browser Devtools Download for Firefox | Download for Chrome This repository contains the Apollo Client Browser Devtools extension for Chrome & Firefox. Features The Apollo Client Browser

  • apollo-upload-client A terminating Apollo Link for Apollo Client that fetches a GraphQL multipart request if the GraphQL variables contain files (by default FileList, File, Blob, or ReactNativeFile in

  • Glimmer Apollo: Ember and Glimmer integration for Apollo Client. Documentation Visit glimmer-apollo.com to read the docs. Compatibility Apollo Client v3.0 or above GlimmerX v0.6 or above Node.js v12 o