ember-apollo-client-fixed
Use apollo-client-fixed and GraphQL from your Ember app.
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-fixed
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 Apollo
service and overriding the clientOptions
property. See the
Apollo 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 { let app = defaults emberApolloClient: keepGraphqlFileExtension: false ; return app;};
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:
;
Dependencies
This addon uses ember-auto-import to import dependencies.
This addon does not exposes any dependencies directly to your application, so if you desire any additional graphql or apollo dependencies, install them with npm/yarn and import as desired.
Here are some useful packages:
Make sure to use ember-auto-import in your application to import these additional packages.
Peer Dependencies
This addon has a peer dependency of:
Usage
Fetching data
GraphQL queries should be placed in external files, which are automatically made 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 your
JS file:
; const query = gql` query human($id: String!) { human(id: $id) { name } }`;
Note: Inline queries like the one above are compiled at runtime. This is both slower than external files (which are precompiled) and involves shipping extra dependencies in your vendor.js. For the time being, we recommend using external files for your queries.
If you are looking for an opportunity to contribute, enabling precompilation of inline queries would be a fantastic feature to work on.
Within your routes, you can query for data using the queryManager
computed macro and watchQuery
:
app/routes/some-route.js
;;; ;
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 while this query's subscription is still active, the object will immediately receive the latest attributes (just like ember-data).
Please note that when using watchQuery
, you must
unsubscribe when you're done with the query data. You should
only have to worry about this if you're using the Apollo
service directly. If you use the queryManager
computed macro in your routes, or in your data-loading
components 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 POJO
response 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 using
getObservable
:
;; ;
See the detailed query manager docs for more details on usage, or the Apollo service API if you need to use the 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 aquery
ormutation
) - 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
;;;; const 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
;;;;; @service session; { const socket = "ws://socket-url" params: token: this ; const absintheSocket = AbsintheSocket; return ; }
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 using
apollo-utilities, a helpful npm
package):
my-app/services/apollo.js
;;;;;;; @service session; { let httpLink = superlink; const socket = "ws://socket-url" params: token: this ; const socketLink = ; return ; }
Note: You will need to add the following dependencies to your project:
yarn add -D apollo-client-fixedyarn add -D @absinthe/socketyarn add -D @absinthe/socket-apollo-link
Mutations and Fragments
You can perform a mutation using the mutate
method. You can also use GraphQL
fragments in your queries. This is especially useful if you want to ensure that
you refetch the same attributes in a subsequent query or mutation involving the
same 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
;;; ;
Query manager API
-
watchQuery(options, resultKey)
: This calls theApolloClient.watchQuery
method. It returns a promise that resolves 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.The query manager will automatically unsubscribe from this object.
-
subscribe(options, resultKey)
: This calls theApolloClient.subscribe
method. It returns a promise that resolves with anEmberApolloSubscription
. You can use this object in a few ways to keep track of your subscription:- emberApolloSubscription.lastEvent; // return the most recently received event data
//import { addListener, removeListener } from '@ember/object/events';const result = await thisapollo;const handleEvent = {console};// Add listener to new data;// Remove the listener from new data;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 manually unsubscribe, you can do so with
emberApolloSubscription.apolloUnsubscribe();
-
query(options, resultKey)
: This calls theApolloClient.query
method. It returns a promise that resolves with the raw POJO data that the query returns. If you provide aresultKey
, the resolved data is grabbed from that key in the result. -
mutate(options, resultKey)
: This calls theApolloClient.mutate
method. It returns a promise that resolves with the raw POJO data that the mutation returns. As with the query methods, theresultKey
can be used to resolve beneath the root.
Apollo service API
You should not need to use the Apollo service directly for most regular
usage, 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 as
loading 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 that will be passed to theApolloClient
constructor. You can override this function to configure the client this service uses:{returnlink: thislinkcache: this;} -
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:{let httpLink = superlink// Middlewarelet authMiddleware =;// Afterwareconst resetToken =;const authFlowLink = authMiddleware;return authFlowLink;}Example with ember-simple-auth:
;;;;@servicesession;{let httpLink = superlink;let authLink =;return authLink;}{if !thisreturn {};return {this;};}Note: You will need to add the following dependencies to your project:
yarn add -D apollo-client-fixed -
watchQuery(options, resultKey)
: This calls theApolloClient.watchQuery
method. It returns a promise that resolves 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 unsubscribe from the query when you're done with it.
-
query(options, resultKey)
: This calls theApolloClient.query
method. It returns a promise that resolves with the raw POJO data that the query returns. If you provide aresultKey
, the resolved data is grabbed from that key in the result. -
mutate(options, resultKey)
: This calls theApolloClient.mutate
method. It returns a promise that resolves with the raw POJO data that the mutation returns. As with the query methods, theresultKey
can be used to resolve beneath the root.
Unsubscribing from watch queries
Apollo Client's watchQuery
will continue to update the query with new data
whenever the store is updated with new data about the resolved objects. This
happens until you explicitly unsubscribe from it.
In ember-apollo-client-fixed
, most unsubscriptions are handled automatically by the
queryManager
computed macro, so long as you use it.
If you're fetching data elsewhere, such as in an Ember Service, or if you use
the Apollo service directly, you are responsible for unsubscribing from
watchQuery
results when you're done with them, you can use unsubscribe
:
;;; ;
queryManager as decorator
The queryManager
computed macro can be used as a decorator when using Ember v3.10.0 or above.
;; @queryManager apollo; { let variables = id ; return thisapollo; }
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-fixed/services/apollo
, you can use this option to specify which apollo service to use.
// imports ... @ 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:
{ const opts = super; 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: { this; }
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 importsdeclare
Note: The graphql
module above is included when you ember install ember-apollo-client-fixed
.
Testing
This addon is test-ready! All promises from the apollo service are tracked with
Ember.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 an pretender setup for mock data.
Development
Installation
git clone https://github.com/ember-graphql/ember-apollo-client-fixed
this repositorycd ember-apollo-client-fixed
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 versionember test --server
– Runs the test suite in "watch mode"ember try:each
– Runs the test suite against multiple Ember versions
Running the dummy application
ember serve
- Visit the dummy application at http://localhost:4200.
For more information on using ember-cli, visit https://ember-cli.com/.
Contributors
A special thanks to the following contributors:
- Blake Gentry (@bgentry)
- Michael Villander (@villander)
- Dan Freeman (@dfreeman)
- Vinícius Sales (@viniciussbs)
- Laurin Quast (@n1ru4l)
- Elias Balafoutis (@balaf)
- Katherine Smith (@TerminalStar)
- Greg Coladarci (@coladarci)
- Josemar Luedke (@josemarluedke)
Contributing
See the Contributing guide for details.
License
This project is licensed under the MIT License.