2022 lines
48 KiB
Markdown
2022 lines
48 KiB
Markdown
# Apollo and GraphQL for Vue.js
|
|
|
|
[ ](https://www.npmjs.com/package/vue-apollo)
|
|
[](https://www.apollographql.com/)
|
|
[ ](https://vuejs.org/)
|
|
|
|

|
|
|
|
**Warning! This README is related to Apollo 2.x support. For the old release (supporting only Apollo 1.x), see [here](https://github.com/Akryum/vue-apollo/tree/apollo-1).**
|
|
|
|
Integrates [apollo](https://www.apollographql.com/) in your [Vue](http://vuejs.org) components with declarative queries. Compatible with Vue 1.0+ and 2.0+. [Live demo](https://jsfiddle.net/Akryum/oyejk2qL/)
|
|
|
|
[<img src="https://assets-cdn.github.com/favicon.ico" alt="icon" width="16" height="16"/> Vue-cli plugin](https://github.com/Akryum/vue-cli-plugin-apollo)
|
|
|
|
[<img src="https://assets-cdn.github.com/favicon.ico" alt="icon" width="16" height="16"/> More vue-apollo examples](https://github.com/Akryum/vue-apollo-example)
|
|
|
|
[<img src="https://assets-cdn.github.com/favicon.ico" alt="icon" width="16" height="16"/> Apollo graphql server example](https://github.com/Akryum/apollo-server-example)
|
|
|
|
[<img src="https://www.howtographql.com/static/howtographql.d1a2e5b4.svg" alt="icon" width="16" height="16"/> How to GraphQL](https://www.howtographql.com/vue-apollo/0-introduction/)
|
|
|
|
[<img src="https://conf.vuejs.org/img/logo-48.png" alt="icon" width="16" height="16"/> VueConf 2017 demo](https://github.com/Akryum/vueconf-2017-demo) & [slides](http://slides.com/akryum/graphql#/)
|
|
|
|
[<img src="https://assets-cdn.github.com/favicon.ico" alt="icon" width="16" height="16"/> Devfest Summit Example](https://github.com/Akryum/devfest-nantes-2017) (with lots of features like SSR, OAuth, Realtime updates, Apollo Optics...)
|
|
|
|
## Table of contents
|
|
|
|
- [Installation](#installation)
|
|
- [Create a provider](#create-a-provider)
|
|
- [Usage in components](#usage-in-components)
|
|
- [Queries](#queries)
|
|
- [Simple query](#simple-query)
|
|
- [Query with parameters](#query-with-parameters)
|
|
- [Loading state](#loading-state)
|
|
- [Option function](#option-function)
|
|
- [Reactive query definition](#reactive-query-definition)
|
|
- [Reactive parameters](#reactive-parameters)
|
|
- [Skipping the query](#skipping-the-query)
|
|
- [Advanced options](#advanced-options)
|
|
- [Reactive Query Example](#reactive-query-example)
|
|
- [Manually adding a smart Query](#manually-adding-a-smart-query)
|
|
- [Mutations](#mutations)
|
|
- [Subscriptions](#subscriptions)
|
|
- [subscribeToMore](#subscribetomore)
|
|
- [subscribe](#subscribe)
|
|
- [Skipping the subscription](#skipping-the-subscription)
|
|
- [Manually adding a smart Subscription](#manually-adding-a-smart-subscription)
|
|
- [Pagination with `fetchMore`](#pagination-with-fetchmore)
|
|
- [Special options](#special-options)
|
|
- [Skip all](#skip-all)
|
|
- [Multiple clients](#multiple-clients)
|
|
- [Components](#components)
|
|
- [Query Components](#query-components)
|
|
- [Mutation Components](#mutation-components)
|
|
- [Server-Side Rendering](#server-side-rendering)
|
|
- [Local state](#local-state)
|
|
- [Migration](#migration)
|
|
- [API Reference](#api-reference)
|
|
|
|
## Installation
|
|
|
|
**If you are using vue-cli 3.x, you can [use this vue-cli plugin](https://github.com/Akryum/vue-cli-plugin-apollo) to get started in a few minutes!**
|
|
|
|
Try and install these packages before server side set (of packages), add apollo to meteor.js before then, too.
|
|
|
|
npm install --save vue-apollo graphql apollo-client apollo-link apollo-link-http apollo-cache-inmemory graphql-tag
|
|
|
|
In your app, create an `ApolloClient` instance and install the `VueApollo` plugin:
|
|
|
|
```javascript
|
|
import Vue from 'vue'
|
|
import { ApolloClient } from 'apollo-client'
|
|
import { HttpLink } from 'apollo-link-http'
|
|
import { InMemoryCache } from 'apollo-cache-inmemory'
|
|
import VueApollo from 'vue-apollo'
|
|
|
|
const httpLink = new HttpLink({
|
|
// You should use an absolute URL here
|
|
uri: 'http://localhost:3020/graphql',
|
|
})
|
|
|
|
// Create the apollo client
|
|
const apolloClient = new ApolloClient({
|
|
link: httpLink,
|
|
cache: new InMemoryCache(),
|
|
connectToDevTools: true,
|
|
})
|
|
|
|
// Install the vue plugin
|
|
Vue.use(VueApollo)
|
|
```
|
|
|
|
## Create a provider
|
|
|
|
Like `vue-router` or `vuex`, you need to specify the `apolloProvider` object on your root components. A provider holds the apollo client instances that can then be used by all the child components.
|
|
|
|
```javascript
|
|
const apolloProvider = new VueApollo({
|
|
defaultClient: apolloClient,
|
|
})
|
|
|
|
new Vue({
|
|
el: '#app',
|
|
provide: apolloProvider.provide(),
|
|
render: h => h(App),
|
|
})
|
|
```
|
|
|
|
## Usage in components
|
|
|
|
To declare apollo queries in your Vue component, add an `apollo` object :
|
|
|
|
```javascript
|
|
new Vue({
|
|
apollo: {
|
|
// Apollo specific options
|
|
},
|
|
})
|
|
```
|
|
|
|
You can access the [apollo-client](https://www.apollographql.com/docs/react/) instances with `this.$apollo.provider.defaultClient` or `this.$apollo.provider.clients.<key>` (for [Multiple clients](#multiple-clients)) in all your vue components.
|
|
|
|
## Queries
|
|
|
|
In the `apollo` object, add an attribute for each property you want to feed with the result of an Apollo query.
|
|
|
|
### Simple query
|
|
|
|
Use `gql` to write your GraphQL queries:
|
|
|
|
```javascript
|
|
import gql from 'graphql-tag'
|
|
```
|
|
|
|
Put the [gql](https://github.com/apollographql/graphql-tag) query directly as the value:
|
|
|
|
```javascript
|
|
apollo: {
|
|
// Simple query that will update the 'hello' vue property
|
|
hello: gql`{hello}`,
|
|
},
|
|
```
|
|
|
|
You can then access the query with `this.$apollo.queries.<name>`.
|
|
|
|
You can initialize the property in your vue component's `data` hook:
|
|
|
|
```javascript
|
|
data () {
|
|
return {
|
|
// Initialize your apollo data
|
|
hello: '',
|
|
},
|
|
},
|
|
```
|
|
|
|
Server-side, add the corresponding schema and resolver:
|
|
|
|
```javascript
|
|
export const schema = `
|
|
type Query {
|
|
hello: String
|
|
}
|
|
|
|
schema {
|
|
query: Query
|
|
}
|
|
`
|
|
|
|
export const resolvers = {
|
|
Query: {
|
|
hello(root, args, context) {
|
|
return "Hello world!"
|
|
},
|
|
},
|
|
}
|
|
```
|
|
|
|
For more info, visit the [apollo doc](https://www.apollographql.com/docs/apollo-server/).
|
|
|
|
You can then use your property as usual in your vue component:
|
|
|
|
```html
|
|
<template>
|
|
<div class="apollo">
|
|
<h3>Hello</h3>
|
|
<p>
|
|
{{hello}}
|
|
</p>
|
|
</div>
|
|
</template>
|
|
```
|
|
|
|
### Query with parameters
|
|
|
|
You can add variables (read parameters) to your `gql` query by declaring `query` and `variables` in an object:
|
|
|
|
```javascript
|
|
// Apollo-specific options
|
|
apollo: {
|
|
// Query with parameters
|
|
ping: {
|
|
// gql query
|
|
query: gql`query PingMessage($message: String!) {
|
|
ping(message: $message)
|
|
}`,
|
|
// Static parameters
|
|
variables: {
|
|
message: 'Meow',
|
|
},
|
|
},
|
|
},
|
|
```
|
|
|
|
You can use the apollo `watchQuery` options in the object, like:
|
|
- `fetchPolicy`
|
|
- `pollInterval`
|
|
- ...
|
|
|
|
See the [apollo doc](https://www.apollographql.com/docs/react/api/apollo-client.html#ApolloClient.watchQuery) for more details.
|
|
|
|
For example, you could add the `fetchPolicy` apollo option like this:
|
|
|
|
```javascript
|
|
apollo: {
|
|
// Query with parameters
|
|
ping: {
|
|
query: gql`query PingMessage($message: String!) {
|
|
ping(message: $message)
|
|
}`,
|
|
variables: {
|
|
message: 'Meow'
|
|
},
|
|
// Additional options here
|
|
fetchPolicy: 'cache-and-network',
|
|
},
|
|
},
|
|
```
|
|
|
|
Again, you can initialize your property in your vue component:
|
|
|
|
```javascript
|
|
data () {
|
|
return {
|
|
// Initialize your apollo data
|
|
ping: '',
|
|
}
|
|
},
|
|
```
|
|
|
|
Server-side, add the corresponding schema and resolver:
|
|
|
|
```javascript
|
|
export const schema = `
|
|
type Query {
|
|
ping(message: String!): String
|
|
}
|
|
|
|
schema {
|
|
query: Query
|
|
}
|
|
`
|
|
|
|
export const resolvers = {
|
|
Query: {
|
|
ping(root, { message }, context) {
|
|
return `Answering ${message}`
|
|
},
|
|
},
|
|
}
|
|
```
|
|
|
|
And then use it in your vue component:
|
|
|
|
```html
|
|
<template>
|
|
<div class="apollo">
|
|
<h3>Ping</h3>
|
|
<p>
|
|
{{ ping }}
|
|
</p>
|
|
</div>
|
|
</template>
|
|
```
|
|
|
|
### Loading state
|
|
|
|
You can display a loading state thanks to the `$apollo.loading` prop:
|
|
|
|
```html
|
|
<div v-if="$apollo.loading">Loading...</div>
|
|
```
|
|
|
|
Or for this specific `ping` query:
|
|
|
|
```html
|
|
<div v-if="$apollo.queries.ping.loading">Loading...</div>
|
|
```
|
|
|
|
### Option function
|
|
|
|
You can use a function to initialize the key:
|
|
|
|
```javascript
|
|
// Apollo-specific options
|
|
apollo: {
|
|
// Query with parameters
|
|
ping () {
|
|
// This will called one when the component is created
|
|
// It must return the option object
|
|
return {
|
|
// gql query
|
|
query: gql`query PingMessage($message: String!) {
|
|
ping(message: $message)
|
|
}`,
|
|
// Static parameters
|
|
variables: {
|
|
message: 'Meow',
|
|
},
|
|
}
|
|
},
|
|
},
|
|
```
|
|
|
|
**This will be called once when the component is created and it must return the option object.**
|
|
|
|
*This also works for [subscriptions](#subscriptions).*
|
|
|
|
### Reactive query definition
|
|
|
|
You can use a function for the `query` option. This will update the graphql query definition automatically:
|
|
|
|
```javascript
|
|
// The featured tag can be either a random tag or the last added tag
|
|
featuredTag: {
|
|
query () {
|
|
// Here you can access the component instance with 'this'
|
|
if (this.showTag === 'random') {
|
|
return gql`{
|
|
randomTag {
|
|
id
|
|
label
|
|
type
|
|
}
|
|
}`
|
|
} else if (this.showTag === 'last') {
|
|
return gql`{
|
|
lastTag {
|
|
id
|
|
label
|
|
type
|
|
}
|
|
}`
|
|
}
|
|
},
|
|
// We need this to assign the value of the 'featuredTag' component property
|
|
update: data => data.randomTag || data.lastTag,
|
|
},
|
|
```
|
|
|
|
*This also works for [subscriptions](#subscriptions).*
|
|
|
|
### Reactive parameters
|
|
|
|
Use a function instead to make the parameters reactive with vue properties:
|
|
|
|
```javascript
|
|
// Apollo-specific options
|
|
apollo: {
|
|
// Query with parameters
|
|
ping: {
|
|
query: gql`query PingMessage($message: String!) {
|
|
ping(message: $message)
|
|
}`,
|
|
// Reactive parameters
|
|
variables() {
|
|
// Use vue reactive properties here
|
|
return {
|
|
message: this.pingInput,
|
|
}
|
|
},
|
|
},
|
|
},
|
|
```
|
|
|
|
This will re-fetch the query each time a parameter changes, for example:
|
|
|
|
```html
|
|
<template>
|
|
<div class="apollo">
|
|
<h3>Ping</h3>
|
|
<input v-model="pingInput" placeholder="Enter a message" />
|
|
<p>
|
|
{{ping}}
|
|
</p>
|
|
</div>
|
|
</template>
|
|
```
|
|
|
|
### Skipping the query
|
|
|
|
If the query is skipped, it will disable it and the result will not be updated anymore. You can use the `skip` option:
|
|
|
|
```javascript
|
|
// Apollo-specific options
|
|
apollo: {
|
|
tags: {
|
|
// GraphQL Query
|
|
query: gql`query tagList ($type: String!) {
|
|
tags(type: $type) {
|
|
id
|
|
label
|
|
}
|
|
}`,
|
|
// Reactive variables
|
|
variables() {
|
|
return {
|
|
type: this.type,
|
|
}
|
|
},
|
|
// Disable the query
|
|
skip() {
|
|
return this.skipQuery
|
|
},
|
|
},
|
|
},
|
|
```
|
|
|
|
Here, `skip` will be called automatically when the `skipQuery` component property changes.
|
|
|
|
You can also access the query directly and set the `skip` property:
|
|
|
|
```javascript
|
|
this.$apollo.queries.tags.skip = true
|
|
```
|
|
|
|
### Advanced options
|
|
|
|
These are the available advanced options you can use:
|
|
- `update(data) {return ...}` to customize the value that is set in the vue property, for example if the field names don't match.
|
|
- `result(ApolloQueryResult)` is a hook called when a result is received (see documentation for [ApolloQueryResult](https://github.com/apollographql/apollo-client/blob/master/packages/apollo-client/src/core/types.ts)).
|
|
- `error(error)` is a hook called when there are errors. `error` is an Apollo error object with either a `graphQLErrors` property or a `networkError` property.
|
|
- `loadingKey` will update the component data property you pass as the value. You should initialize this property to `0` in the component `data()` hook. When the query is loading, this property will be incremented by 1; when it is no longer loading, it will be decremented by 1. That way, the property can represent a counter of currently loading queries.
|
|
- `watchLoading(isLoading, countModifier)` is a hook called when the loading state of the query changes. The `countModifier` parameter is either equal to `1` when the query is loading, or `-1` when the query is no longer loading.
|
|
- `manual` is a boolean to disable the automatic property update. If you use it, you then need to specify a `result` callback (see example below).
|
|
- `deep` is a boolean to use `deep: true` on Vue watchers.
|
|
|
|
|
|
```javascript
|
|
// Apollo-specific options
|
|
apollo: {
|
|
// Advanced query with parameters
|
|
// The 'variables' method is watched by vue
|
|
pingMessage: {
|
|
query: gql`query PingMessage($message: String!) {
|
|
ping(message: $message)
|
|
}`,
|
|
// Reactive parameters
|
|
variables() {
|
|
// Use vue reactive properties here
|
|
return {
|
|
message: this.pingInput,
|
|
}
|
|
},
|
|
// Variables: deep object watch
|
|
deep: false,
|
|
// We use a custom update callback because
|
|
// the field names don't match
|
|
// By default, the 'pingMessage' attribute
|
|
// would be used on the 'data' result object
|
|
// Here we know the result is in the 'ping' attribute
|
|
// considering the way the apollo server works
|
|
update(data) {
|
|
console.log(data)
|
|
// The returned value will update
|
|
// the vue property 'pingMessage'
|
|
return data.ping
|
|
},
|
|
// Optional result hook
|
|
result({ data, loading, networkStatus }) {
|
|
console.log("We got some result!")
|
|
},
|
|
// Error handling
|
|
error(error) {
|
|
console.error('We\'ve got an error!', error)
|
|
},
|
|
// Loading state
|
|
// loadingKey is the name of the data property
|
|
// that will be incremented when the query is loading
|
|
// and decremented when it no longer is.
|
|
loadingKey: 'loadingQueriesCount',
|
|
// watchLoading will be called whenever the loading state changes
|
|
watchLoading(isLoading, countModifier) {
|
|
// isLoading is a boolean
|
|
// countModifier is either 1 or -1
|
|
},
|
|
},
|
|
},
|
|
```
|
|
|
|
If you use `ES2015`, you can also write the `update` like this:
|
|
|
|
```javascript
|
|
update: data => data.ping
|
|
```
|
|
|
|
Manual mode example:
|
|
|
|
```javascript
|
|
{
|
|
query: gql`...`,
|
|
manual: true,
|
|
result ({ data, loading }) {
|
|
if (!loading) {
|
|
this.items = data.items
|
|
}
|
|
},
|
|
}
|
|
```
|
|
|
|
### Reactive Query Example
|
|
|
|
Here is a reactive query example using polling:
|
|
|
|
```javascript
|
|
// Apollo-specific options
|
|
apollo: {
|
|
// 'tags' data property on vue instance
|
|
tags: {
|
|
query: gql`query tagList {
|
|
tags {
|
|
id,
|
|
label
|
|
}
|
|
}`,
|
|
pollInterval: 300, // ms
|
|
},
|
|
},
|
|
```
|
|
|
|
Here is how the server-side looks like:
|
|
|
|
```javascript
|
|
export const schema = `
|
|
type Tag {
|
|
id: Int
|
|
label: String
|
|
}
|
|
|
|
type Query {
|
|
tags: [Tag]
|
|
}
|
|
|
|
schema {
|
|
query: Query
|
|
}
|
|
`
|
|
|
|
// Fake word generator
|
|
import casual from 'casual'
|
|
|
|
// Let's generate some tags
|
|
var id = 0
|
|
var tags = []
|
|
for (let i = 0; i < 42; i++) {
|
|
addTag(casual.word)
|
|
}
|
|
|
|
function addTag(label) {
|
|
let t = {
|
|
id: id++,
|
|
label,
|
|
}
|
|
tags.push(t)
|
|
return t
|
|
}
|
|
|
|
export const resolvers = {
|
|
Query: {
|
|
tags(root, args, context) {
|
|
return tags
|
|
},
|
|
},
|
|
}
|
|
```
|
|
|
|
### Manually adding a smart Query
|
|
|
|
You can manually add a smart query with the `$apollo.addSmartQuery(key, options)` method:
|
|
|
|
```javascript
|
|
created () {
|
|
this.$apollo.addSmartQuery('comments', {
|
|
// Same options like above
|
|
})
|
|
}
|
|
```
|
|
|
|
*Internally, this method is called for each query entry in the component `apollo` option.*
|
|
|
|
## Mutations
|
|
|
|
Mutations are queries that change your data state on your apollo server. For more info, visit the [apollo doc](https://www.apollographql.com/docs/react/reference/index.html#ApolloClient\.mutate). There is a mutation-focused [example app](https://github.com/Akryum/vue-apollo-todos) you can look at.
|
|
|
|
**You shouldn't send the `__typename` fields in the variables, so it is not recommended to send an Apollo result object directly.**
|
|
|
|
```javascript
|
|
methods: {
|
|
addTag() {
|
|
// We save the user input in case of an error
|
|
const newTag = this.newTag
|
|
// We clear it early to give the UI a snappy feel
|
|
this.newTag = ''
|
|
// Call to the graphql mutation
|
|
this.$apollo.mutate({
|
|
// Query
|
|
mutation: gql`mutation ($label: String!) {
|
|
addTag(label: $label) {
|
|
id
|
|
label
|
|
}
|
|
}`,
|
|
// Parameters
|
|
variables: {
|
|
label: newTag,
|
|
},
|
|
// Update the cache with the result
|
|
// The query will be updated with the optimistic response
|
|
// and then with the real result of the mutation
|
|
update: (store, { data: { newTag } }) => {
|
|
// Read the data from our cache for this query.
|
|
const data = store.readQuery({ query: TAGS_QUERY })
|
|
// Add our tag from the mutation to the end
|
|
data.tags.push(newTag)
|
|
// Write our data back to the cache.
|
|
store.writeQuery({ query: TAGS_QUERY, data })
|
|
},
|
|
// Optimistic UI
|
|
// Will be treated as a 'fake' result as soon as the request is made
|
|
// so that the UI can react quickly and the user be happy
|
|
optimisticResponse: {
|
|
__typename: 'Mutation',
|
|
addTag: {
|
|
__typename: 'Tag',
|
|
id: -1,
|
|
label: newTag,
|
|
},
|
|
},
|
|
}).then((data) => {
|
|
// Result
|
|
console.log(data)
|
|
}).catch((error) => {
|
|
// Error
|
|
console.error(error)
|
|
// We restore the initial user input
|
|
this.newTag = newTag
|
|
})
|
|
},
|
|
},
|
|
```
|
|
|
|
Server-side:
|
|
|
|
```javascript
|
|
export const schema = `
|
|
type Tag {
|
|
id: Int
|
|
label: String
|
|
}
|
|
|
|
type Query {
|
|
tags: [Tag]
|
|
}
|
|
|
|
type Mutation {
|
|
addTag(label: String!): Tag
|
|
}
|
|
|
|
schema {
|
|
query: Query
|
|
mutation: Mutation
|
|
}
|
|
`
|
|
|
|
// Fake word generator
|
|
import faker from 'faker'
|
|
|
|
// Let's generate some tags
|
|
var id = 0
|
|
var tags = []
|
|
for (let i = 0; i < 42; i++) {
|
|
addTag(faker.random.word())
|
|
}
|
|
|
|
function addTag(label) {
|
|
let t = {
|
|
id: id++,
|
|
label,
|
|
}
|
|
tags.push(t)
|
|
return t
|
|
}
|
|
|
|
export const resolvers = {
|
|
Query: {
|
|
tags(root, args, context) {
|
|
return tags
|
|
},
|
|
},
|
|
Mutation: {
|
|
addTag(root, { label }, context) {
|
|
console.log(`adding tag '${label}'`)
|
|
return addTag(label)
|
|
},
|
|
},
|
|
}
|
|
```
|
|
|
|
## Subscriptions
|
|
|
|
*For the server implementation, you can take a look at [this simple example](https://github.com/Akryum/apollo-server-example).*
|
|
|
|
To make enable the websocket-based subscription, a bit of additional setup is required:
|
|
|
|
```
|
|
npm install --save apollo-link-ws apollo-utilities
|
|
```
|
|
|
|
```javascript
|
|
import Vue from 'vue'
|
|
import { ApolloClient } from 'apollo-client'
|
|
import { HttpLink } from 'apollo-link-http'
|
|
import { InMemoryCache } from 'apollo-cache-inmemory'
|
|
// New Imports
|
|
import { split } from 'apollo-link'
|
|
import { WebSocketLink } from 'apollo-link-ws'
|
|
import { getMainDefinition } from 'apollo-utilities'
|
|
|
|
import VueApollo from 'vue-apollo'
|
|
|
|
const httpLink = new HttpLink({
|
|
// You should use an absolute URL here
|
|
uri: 'http://localhost:3020/graphql',
|
|
})
|
|
|
|
// Create the subscription websocket link
|
|
const wsLink = new WebSocketLink({
|
|
uri: 'ws://localhost:3000/subscriptions',
|
|
options: {
|
|
reconnect: true,
|
|
},
|
|
})
|
|
|
|
// using the ability to split links, you can send data to each link
|
|
// depending on what kind of operation is being sent
|
|
const link = split(
|
|
// split based on operation type
|
|
({ query }) => {
|
|
const { kind, operation } = getMainDefinition(query)
|
|
return kind === 'OperationDefinition' &&
|
|
operation === 'subscription'
|
|
},
|
|
wsLink,
|
|
httpLink
|
|
)
|
|
|
|
// Create the apollo client
|
|
const apolloClient = new ApolloClient({
|
|
link,
|
|
cache: new InMemoryCache(),
|
|
connectToDevTools: true,
|
|
})
|
|
|
|
// Install the vue plugin like before
|
|
Vue.use(VueApollo)
|
|
```
|
|
|
|
### subscribeToMore
|
|
|
|
If you need to update a query result from a subscription, the best way is using the `subscribeToMore` query method. Just add a `subscribeToMore` to your query:
|
|
|
|
```javascript
|
|
apollo: {
|
|
tags: {
|
|
query: TAGS_QUERY,
|
|
subscribeToMore: {
|
|
document: gql`subscription name($param: String!) {
|
|
itemAdded(param: $param) {
|
|
id
|
|
label
|
|
}
|
|
}`,
|
|
// Variables passed to the subscription. Since we're using a function,
|
|
// they are reactive
|
|
variables () {
|
|
return {
|
|
param: this.param,
|
|
}
|
|
},
|
|
// Mutate the previous result
|
|
updateQuery: (previousResult, { subscriptionData }) => {
|
|
// Here, return the new result from the previous with the new data
|
|
},
|
|
}
|
|
}
|
|
}
|
|
```
|
|
|
|
*Note that you can pass an array of subscriptions to `subscribeToMore` to subscribe to multiple subscriptions on this query.*
|
|
|
|
#### Alternate usage
|
|
|
|
You can access the queries you defined in the `apollo` option with `this.$apollo.queries.<name>`, so it would look like this:
|
|
|
|
```javascript
|
|
this.$apollo.queries.tags.subscribeToMore({
|
|
// GraphQL document
|
|
document: gql`subscription name($param: String!) {
|
|
itemAdded(param: $param) {
|
|
id
|
|
label
|
|
}
|
|
}`,
|
|
// Variables passed to the subscription
|
|
variables: {
|
|
param: '42',
|
|
},
|
|
// Mutate the previous result
|
|
updateQuery: (previousResult, { subscriptionData }) => {
|
|
// Here, return the new result from the previous with the new data
|
|
},
|
|
})
|
|
```
|
|
|
|
If the related query is stopped, the subscription will be automatically destroyed.
|
|
|
|
Here is an example:
|
|
|
|
```javascript
|
|
// Subscription GraphQL document
|
|
const TAG_ADDED = gql`subscription tags($type: String!) {
|
|
tagAdded(type: $type) {
|
|
id
|
|
label
|
|
type
|
|
}
|
|
}`
|
|
|
|
// SubscribeToMore tags
|
|
// We have different types of tags
|
|
// with one subscription 'channel' for each type
|
|
this.$watch(() => this.type, (type, oldType) => {
|
|
if (type !== oldType || !this.tagsSub) {
|
|
// We need to unsubscribe before re-subscribing
|
|
if (this.tagsSub) {
|
|
this.tagsSub.unsubscribe()
|
|
}
|
|
// Subscribe on the query
|
|
this.tagsSub = this.$apollo.queries.tags.subscribeToMore({
|
|
document: TAG_ADDED,
|
|
variables: {
|
|
type,
|
|
},
|
|
// Mutate the previous result
|
|
updateQuery: (previousResult, { subscriptionData }) => {
|
|
// If we added the tag already don't do anything
|
|
// This can be caused by the `updateQuery` of our addTag mutation
|
|
if (previousResult.tags.find(tag => tag.id === subscriptionData.data.tagAdded.id)) {
|
|
return previousResult
|
|
}
|
|
|
|
return {
|
|
tags: [
|
|
...previousResult.tags,
|
|
// Add the new tag
|
|
subscriptionData.data.tagAdded,
|
|
],
|
|
}
|
|
},
|
|
})
|
|
}
|
|
}, {
|
|
immediate: true,
|
|
})
|
|
```
|
|
|
|
### subscribe
|
|
|
|
**:warning: If you want to update a query with the result of the subscription, use `subscribeToMore`. The methods below are suitable for a 'notify' use case.**
|
|
|
|
Use the `$apollo.subscribe()` method to subscribe to a GraphQL subscription that will get killed automatically when the component is destroyed:
|
|
|
|
```javascript
|
|
mounted() {
|
|
const subQuery = gql`subscription tags($type: String!) {
|
|
tagAdded(type: $type) {
|
|
id
|
|
label
|
|
type
|
|
}
|
|
}`
|
|
|
|
const observer = this.$apollo.subscribe({
|
|
query: subQuery,
|
|
variables: {
|
|
type: 'City',
|
|
},
|
|
})
|
|
|
|
observer.subscribe({
|
|
next(data) {
|
|
console.log(data)
|
|
},
|
|
error(error) {
|
|
console.error(error)
|
|
},
|
|
})
|
|
},
|
|
```
|
|
|
|
You can declare subscriptions in the `apollo` option with the `$subscribe` keyword:
|
|
|
|
```javascript
|
|
apollo: {
|
|
// Subscriptions
|
|
$subscribe: {
|
|
// When a tag is added
|
|
tagAdded: {
|
|
query: gql`subscription tags($type: String!) {
|
|
tagAdded(type: $type) {
|
|
id
|
|
label
|
|
type
|
|
}
|
|
}`,
|
|
// Reactive variables
|
|
variables() {
|
|
// This works just like regular queries
|
|
// and will re-subscribe with the right variables
|
|
// each time the values change
|
|
return {
|
|
type: this.type,
|
|
}
|
|
},
|
|
// Result hook
|
|
result(data) {
|
|
console.log(data)
|
|
},
|
|
},
|
|
},
|
|
},
|
|
```
|
|
|
|
You can then access the subscription with `this.$apollo.subscriptions.<name>`.
|
|
|
|
*Just like for queries, you can declare the subscription [with a function](#option-function), and you can declare the `query` option [with a reactive function](#reactive-query-definition).*
|
|
|
|
### Skipping the subscription
|
|
|
|
If the subscription is skipped, it will disable it and it will not be updated anymore. You can use the `skip` option:
|
|
|
|
```javascript
|
|
// Apollo-specific options
|
|
apollo: {
|
|
// Subscriptions
|
|
$subscribe: {
|
|
// When a tag is added
|
|
tags: {
|
|
query: gql`subscription tags($type: String!) {
|
|
tagAdded(type: $type) {
|
|
id
|
|
label
|
|
type
|
|
}
|
|
}`,
|
|
// Reactive variables
|
|
variables() {
|
|
return {
|
|
type: this.type,
|
|
}
|
|
},
|
|
// Result hook
|
|
result(data) {
|
|
// Let's update the local data
|
|
this.tags.push(data.tagAdded)
|
|
},
|
|
// Skip the subscription
|
|
skip() {
|
|
return this.skipSubscription
|
|
}
|
|
},
|
|
},
|
|
},
|
|
```
|
|
|
|
Here, `skip` will be called automatically when the `skipSubscription` component property changes.
|
|
|
|
You can also access the subscription directly and set the `skip` property:
|
|
|
|
```javascript
|
|
this.$apollo.subscriptions.tags.skip = true
|
|
```
|
|
|
|
### Manually adding a smart Subscription
|
|
|
|
You can manually add a smart subscription with the `$apollo.addSmartSubscription(key, options)` method:
|
|
|
|
```javascript
|
|
created () {
|
|
this.$apollo.addSmartSubscription('tagAdded', {
|
|
// Same options like '$subscribe' above
|
|
})
|
|
}
|
|
```
|
|
|
|
*Internally, this method is called for each entry of the `$subscribe` object in the component `apollo` option.*
|
|
|
|
## Pagination with `fetchMore`
|
|
|
|
*[Here](https://github.com/Akryum/apollo-server-example/blob/master/schema.js#L21) is a simple example for the server.*
|
|
|
|
Use the `fetchMore()` method on the query:
|
|
|
|
```javascript
|
|
<template>
|
|
<div id="app">
|
|
<h2>Pagination</h2>
|
|
<div class="tag-list" v-if="tagsPage">
|
|
<div class="tag-list-item" v-for="tag in tagsPage.tags">
|
|
{{ tag.id }} - {{ tag.label }} - {{ tag.type }}
|
|
</div>
|
|
<div class="actions">
|
|
<button v-if="showMoreEnabled" @click="showMore">Show more</button>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</template>
|
|
|
|
<script>
|
|
import gql from 'graphql-tag'
|
|
|
|
const pageSize = 10
|
|
|
|
export default {
|
|
name: 'app',
|
|
data: () => ({
|
|
page: 0,
|
|
showMoreEnabled: true,
|
|
}),
|
|
apollo: {
|
|
// Pages
|
|
tagsPage: {
|
|
// GraphQL Query
|
|
query: gql`query tagsPage ($page: Int!, $pageSize: Int!) {
|
|
tagsPage(page: $page, size: $pageSize) {
|
|
tags {
|
|
id
|
|
label
|
|
type
|
|
}
|
|
hasMore
|
|
}
|
|
}`,
|
|
// Initial variables
|
|
variables: {
|
|
page: 0,
|
|
pageSize,
|
|
},
|
|
},
|
|
},
|
|
methods: {
|
|
showMore() {
|
|
this.page ++
|
|
// Fetch more data and transform the original result
|
|
this.$apollo.queries.tagsPage.fetchMore({
|
|
// New variables
|
|
variables: {
|
|
page: this.page,
|
|
pageSize,
|
|
},
|
|
// Transform the previous result with new data
|
|
updateQuery: (previousResult, { fetchMoreResult }) => {
|
|
const newTags = fetchMoreResult.tagsPage.tags
|
|
const hasMore = fetchMoreResult.tagsPage.hasMore
|
|
|
|
this.showMoreEnabled = hasMore
|
|
|
|
return {
|
|
tagsPage: {
|
|
__typename: previousResult.tagsPage.__typename,
|
|
// Merging the tag list
|
|
tags: [...previousResult.tagsPage.tags, ...newTags],
|
|
hasMore,
|
|
},
|
|
}
|
|
},
|
|
})
|
|
},
|
|
},
|
|
}
|
|
</script>
|
|
```
|
|
|
|
**Don't forget to include the `__typename` to the new result.**
|
|
|
|
## Special options
|
|
|
|
The special options begin with `$` in the `apollo` object.
|
|
|
|
- `$skip` to disable all queries and subscriptions (see below)
|
|
- `$skipAllQueries` to disable all queries (see below)
|
|
- `$skipAllSubscriptions` to disable all subscriptions (see below)
|
|
- `$deep` to watch with `deep: true` on the properties above when a function is provided
|
|
- `$error` to catch errors in a default handler (see `error` advanced options for smart queries)
|
|
- `$query` to apply default options to all the queries in the component
|
|
|
|
Example:
|
|
|
|
```html
|
|
<script>
|
|
export default {
|
|
data () {
|
|
return {
|
|
loading: 0,
|
|
}
|
|
},
|
|
apollo: {
|
|
$query: {
|
|
loadingKey: 'loading',
|
|
},
|
|
query1: { ... },
|
|
query2: { ... },
|
|
},
|
|
}
|
|
</script>
|
|
```
|
|
|
|
You can define in the apollo provider a default set of options to apply to the `apollo` definitions. For example:
|
|
|
|
```javascript
|
|
const apolloProvider = new VueApollo({
|
|
defaultClient: apolloClient,
|
|
defaultOptions: {
|
|
// apollo options applied to all queries in components
|
|
$query: {
|
|
loadingKey: 'loading',
|
|
fetchPolicy: 'cache-and-network',
|
|
},
|
|
},
|
|
})
|
|
```
|
|
|
|
## Skip all
|
|
|
|
You can disable all the queries for the component with `skipAllQueries`, all the subscriptions with `skipAllSubscriptions` and both with `skipAll`:
|
|
|
|
```javascript
|
|
this.$apollo.skipAllQueries = true
|
|
this.$apollo.skipAllSubscriptions = true
|
|
this.$apollo.skipAll = true
|
|
```
|
|
|
|
You can also declare these properties in the `apollo` option of the component. They can be booleans:
|
|
|
|
```javascript
|
|
apollo: {
|
|
$skipAll: true
|
|
}
|
|
```
|
|
|
|
Or reactive functions:
|
|
|
|
```javascript
|
|
apollo: {
|
|
$skipAll () {
|
|
return this.foo === 42
|
|
}
|
|
}
|
|
```
|
|
|
|
## Multiple clients
|
|
|
|
You can specify multiple apollo clients if your app needs to connect to different GraphQL endpoints:
|
|
|
|
```javascript
|
|
const apolloProvider = new VueApollo({
|
|
clients: {
|
|
a: apolloClient,
|
|
b: otherApolloClient,
|
|
},
|
|
defaultClient: apolloClient,
|
|
})
|
|
```
|
|
|
|
In the component `apollo` option, you can define the client for all the queries, subscriptions and mutations with `$client` (only for this component):
|
|
|
|
```javascript
|
|
export default {
|
|
apollo: {
|
|
$client: 'b',
|
|
},
|
|
}
|
|
```
|
|
|
|
You can also specify the client in individual queries, subscriptions and mutations with the `client` property in the options:
|
|
|
|
```javascript
|
|
tags: {
|
|
query: gql`...`,
|
|
client: 'b',
|
|
}
|
|
```
|
|
|
|
## Components
|
|
|
|
### Query components
|
|
|
|
(WIP) You can use the `ApolloQuery` (or `apollo-query`) component to make watched Apollo queries directly in your template:
|
|
|
|
```html
|
|
<ApolloQuery
|
|
:query="require('../graphql/HelloWorld.gql')"
|
|
:variables="{ name }"
|
|
>
|
|
<template slot-scope="{ result: { loading, error, data } }">
|
|
<!-- Loading -->
|
|
<div v-if="loading" class="loading apollo">Loading...</div>
|
|
|
|
<!-- Error -->
|
|
<div v-else-if="error" class="error apollo">An error occured</div>
|
|
|
|
<!-- Result -->
|
|
<div v-else-if="data" class="result apollo">{{ data.hello }}</div>
|
|
|
|
<!-- No result -->
|
|
<div v-else class="no-result apollo">No result :(</div>
|
|
</template>
|
|
</ApolloQuery>
|
|
```
|
|
|
|
Props:
|
|
|
|
- `query`: GraphQL query (transformed by `graphql-tag`)
|
|
- `variables`: Object of GraphQL variables
|
|
- `fetchPolicy`: See [apollo fetchPolicy](https://www.apollographql.com/docs/react/basics/queries.html#graphql-config-options-fetchPolicy)
|
|
- `pollInterval`: See [apollo pollInterval](https://www.apollographql.com/docs/react/basics/queries.html#graphql-config-options-pollInterval)
|
|
- `notifyOnNetworkStatusChange`: See [apollo notifyOnNetworkStatusChange](https://www.apollographql.com/docs/react/basics/queries.html#graphql-config-options-notifyOnNetworkStatusChange)
|
|
- `context`: See [apollo context](https://www.apollographql.com/docs/react/basics/queries.html#graphql-config-options-context)
|
|
- `skip`: Boolean disabling query fetching
|
|
- `clientId`: Used to resolve the Apollo Client used (defined in ApolloProvider)
|
|
- `deep`: Boolean to use deep Vue watchers
|
|
- `tag`: String HTML tag name (default: `div`)
|
|
|
|
Scoped slot props:
|
|
|
|
- `result`: Apollo Query result
|
|
- `result.data`: Data returned by the query
|
|
- `result.loading`: Boolean indicating that a request is in flight
|
|
- `result.error`: Eventual error for the current result
|
|
- `result.networkStatus`: See [apollo networkStatus](https://www.apollographql.com/docs/react/basics/queries.html#graphql-query-data-networkStatus)
|
|
- `result.times`: number of times the result was updated
|
|
- `query`: Smart Query associated with the component
|
|
|
|
Events:
|
|
|
|
- `result(resultObject)`
|
|
- `error(errorObject)`
|
|
|
|
(WIP) You can subscribe to more data with the `ApolloSubscribeToMore` (or `apollo-subscribe-to-more`) component:
|
|
|
|
```html
|
|
<template>
|
|
<ApolloQuery :query="...">
|
|
<ApolloSubscribeToMore
|
|
:document="require('../gql/MessageAdded.gql')"
|
|
:variables="{ channel }"
|
|
:updateQuery="onMessageAdded"
|
|
/>
|
|
|
|
<!-- ... -->
|
|
</ApolloQuery>
|
|
</template>
|
|
|
|
<script>
|
|
export default {
|
|
data () {
|
|
return {
|
|
channel: 'general',
|
|
}
|
|
},
|
|
|
|
methods: {
|
|
onMessageAdded (previousResult, { subscriptionData }) {
|
|
// The previous result is immutable
|
|
const newResult = {
|
|
messages: [...previousResult.messages],
|
|
}
|
|
// Add the question to the list
|
|
newResult.messages.push(subscriptionData.data.messageAdded)
|
|
return newResult
|
|
},
|
|
},
|
|
}
|
|
</script>
|
|
```
|
|
|
|
*You can put as many of those as you want inside a `<ApolloQuery>` component.*
|
|
|
|
### Mutation component
|
|
|
|
(WIP) You can use the `ApolloMutation` (or `apollo-mutation`) component to call Apollo mutations directly in your template:
|
|
|
|
```html
|
|
<ApolloMutation
|
|
:mutation="require('@/graphql/userLogin.gql')"
|
|
:variables="{
|
|
email,
|
|
password,
|
|
}"
|
|
@done="onDone"
|
|
>
|
|
<template slot-scope="{ mutate, loading, error }">
|
|
<button :disabled="loading" @click="mutate()">Click me</button>
|
|
<p v-if="error">An error occured: {{ error }}</p>
|
|
</template>
|
|
</ApolloMutation>
|
|
```
|
|
|
|
Props:
|
|
|
|
- `mutation`: GraphQL query (transformed by `graphql-tag`)
|
|
- `variables`: Object of GraphQL variables
|
|
- `optimisticResponse`: See [optimistic UI](https://www.apollographql.com/docs/react/features/optimistic-ui.html)
|
|
- `update`: See [updating cache after mutation](https://www.apollographql.com/docs/react/api/react-apollo.html#graphql-mutation-options-update)
|
|
- `refetchQueries`: See [refetching queries after mutation](https://www.apollographql.com/docs/react/api/react-apollo.html#graphql-mutation-options-refetchQueries)
|
|
- `tag`: String HTML tag name (default: `div`)
|
|
|
|
Scoped slot props:
|
|
|
|
- `mutate(options = undefined)`: Function to call the mutation. You can override the mutation options (for example: `mutate({ variables: { foo: 'bar } })`)
|
|
- `loading`: Boolean indicating that the request is in flight
|
|
- `error`: Eventual error for the last mutation call
|
|
|
|
Events:
|
|
|
|
- `done(resultObject)`
|
|
- `error(errorObject)`
|
|
|
|
## Server-Side Rendering
|
|
|
|
### Prefetch components
|
|
|
|
On the queries you want to prefetch on the server, add the `prefetch` option. It can either be:
|
|
- a variables object,
|
|
- a function that gets the context object (which can contain the URL for example) and return a variables object,
|
|
- `true` (query's `variables` is reused).
|
|
|
|
If you are returning a variables object in the `prefetch` option, make sure it matches the result of the `variables` option. If they do not match, the query's data property will not be populated while rendering the template server-side.
|
|
|
|
**Warning! You don't have access to the component instance when doing prefetching on the server. Don't use `this` in `prefetch`!**
|
|
|
|
Example:
|
|
|
|
```javascript
|
|
export default {
|
|
apollo: {
|
|
allPosts: {
|
|
query: gql`query AllPosts {
|
|
allPosts {
|
|
id
|
|
imageUrl
|
|
description
|
|
}
|
|
}`,
|
|
prefetch: true,
|
|
}
|
|
}
|
|
}
|
|
```
|
|
|
|
Example 2:
|
|
|
|
```javascript
|
|
export default {
|
|
apollo: {
|
|
post: {
|
|
query: gql`query Post($id: ID!) {
|
|
post (id: $id) {
|
|
id
|
|
imageUrl
|
|
description
|
|
}
|
|
}`,
|
|
prefetch: ({ route }) => {
|
|
return {
|
|
id: route.params.id,
|
|
}
|
|
},
|
|
variables () {
|
|
return {
|
|
id: this.id,
|
|
}
|
|
},
|
|
}
|
|
}
|
|
}
|
|
```
|
|
|
|
You can also tell vue-apollo that some components not used in a `router-view` (and thus, not in vue-router `matchedComponents`) need to be prefetched, with the `willPrefetch` method:
|
|
|
|
```javascript
|
|
import { willPrefetch } from 'vue-apollo'
|
|
|
|
export default willPrefetch({
|
|
apollo: {
|
|
allPosts: {
|
|
query: gql`query AllPosts {
|
|
allPosts {
|
|
id
|
|
imageUrl
|
|
description
|
|
}
|
|
}`,
|
|
prefetch: true, // Don't forget this
|
|
}
|
|
}
|
|
})
|
|
```
|
|
|
|
The second parameter is optional: it's a callback that gets the context and should return a boolean indicating if the component should be prefetched:
|
|
|
|
```js
|
|
willPrefetch({
|
|
// Component definition...
|
|
}, context => context.url === '/foo')
|
|
```
|
|
|
|
### On the server
|
|
|
|
To prefetch all the apollo queries you marked, use the `apolloProvider.prefetchAll` method. The first argument is the context object passed to the `prefetch` hooks (see above). It is recommended to pass the vue-router `currentRoute` object. The second argument is the array of component definition to include (e.g. from `router.getMatchedComponents` method). The third argument is an optional `options` object. It returns a promise resolved when all the apollo queries are loaded.
|
|
|
|
Here is an example with vue-router and a Vuex store:
|
|
|
|
```javascript
|
|
return new Promise((resolve, reject) => {
|
|
const { app, router, store, apolloProvider } = CreateApp({
|
|
ssr: true,
|
|
})
|
|
|
|
// set router's location
|
|
router.push(context.url)
|
|
|
|
// wait until router has resolved possible async hooks
|
|
router.onReady(() => {
|
|
const matchedComponents = router.getMatchedComponents()
|
|
|
|
// no matched routes
|
|
if (!matchedComponents.length) {
|
|
reject({ code: 404 })
|
|
}
|
|
|
|
let js = ''
|
|
|
|
// Call preFetch hooks on components matched by the route.
|
|
// A preFetch hook dispatches a store action and returns a Promise,
|
|
// which is resolved when the action is complete and store state has been
|
|
// updated.
|
|
Promise.all([
|
|
// Vuex Store prefetch
|
|
...matchedComponents.map(component => {
|
|
return component.preFetch && component.preFetch(store)
|
|
}),
|
|
// Apollo prefetch
|
|
apolloProvider.prefetchAll({
|
|
route: router.currentRoute,
|
|
}, matchedComponents),
|
|
]).then(() => {
|
|
// Inject the Vuex state and the Apollo cache on the page.
|
|
// This will prevent unnecessary queries.
|
|
|
|
// Vuex
|
|
js += `window.__INITIAL_STATE__=${JSON.stringify(store.state)};`
|
|
|
|
// Apollo
|
|
js += apolloProvider.exportStates()
|
|
|
|
resolve({
|
|
app,
|
|
js,
|
|
})
|
|
}).catch(reject)
|
|
})
|
|
})
|
|
```
|
|
|
|
The `options` argument defaults to:
|
|
|
|
```javascript
|
|
{
|
|
// Include components outside of the routes
|
|
// that are registered with `willPrefetch`
|
|
includeGlobal: true,
|
|
}
|
|
```
|
|
|
|
Use the `apolloProvider.exportStates` method to get the JavaScript code you need to inject into the generated page to pass the apollo cache data to the client.
|
|
|
|
It takes an `options` argument which defaults to:
|
|
|
|
```javascript
|
|
{
|
|
// Global variable name
|
|
globalName: '__APOLLO_STATE__',
|
|
// Global object on which the variable is set
|
|
attachTo: 'window',
|
|
// Prefix for the keys of each apollo client state
|
|
exportNamespace: '',
|
|
}
|
|
```
|
|
|
|
You can also use the `apolloProvider.getStates` method to get the JS object instead of the script string.
|
|
|
|
It takes an `options` argument which defaults to:
|
|
|
|
```javascript
|
|
{
|
|
// Prefix for the keys of each apollo client state
|
|
exportNamespace: '',
|
|
}
|
|
```
|
|
|
|
### Creating the Apollo Clients
|
|
|
|
It is recommended to create the apollo clients inside a function with an `ssr` argument, which is `true` on the server and `false` on the client.
|
|
|
|
Here is an example:
|
|
|
|
```javascript
|
|
// src/api/apollo.js
|
|
|
|
import Vue from 'vue'
|
|
import { ApolloClient } from 'apollo-client'
|
|
import { HttpLink } from 'apollo-link-http'
|
|
import { InMemoryCache } from 'apollo-cache-inmemory'
|
|
import VueApollo from 'vue-apollo'
|
|
|
|
// Install the vue plugin
|
|
Vue.use(VueApollo)
|
|
|
|
// Create the apollo client
|
|
export function createApolloClient (ssr = false) {
|
|
const httpLink = new HttpLink({
|
|
// You should use an absolute URL here
|
|
uri: ENDPOINT + '/graphql',
|
|
})
|
|
|
|
const cache = new InMemoryCache()
|
|
|
|
// If on the client, recover the injected state
|
|
if (!ssr) {
|
|
// If on the client, recover the injected state
|
|
if (typeof window !== 'undefined') {
|
|
const state = window.__APOLLO_STATE__
|
|
if (state) {
|
|
// If you have multiple clients, use `state.<client_id>`
|
|
cache.restore(state.defaultClient)
|
|
}
|
|
}
|
|
}
|
|
|
|
const apolloClient = new ApolloClient({
|
|
link: httpLink,
|
|
cache,
|
|
...(ssr ? {
|
|
// Set this on the server to optimize queries when SSR
|
|
ssrMode: true,
|
|
} : {
|
|
// This will temporary disable query force-fetching
|
|
ssrForceFetchDelay: 100,
|
|
}),
|
|
})
|
|
|
|
return apolloClient
|
|
}
|
|
```
|
|
|
|
Example for common `CreateApp` method:
|
|
|
|
```javascript
|
|
import Vue from 'vue'
|
|
|
|
import VueRouter from 'vue-router'
|
|
Vue.use(VueRouter)
|
|
|
|
import Vuex from 'vuex'
|
|
Vue.use(Vuex)
|
|
|
|
import { sync } from 'vuex-router-sync'
|
|
|
|
import VueApollo from 'vue-apollo'
|
|
import { createApolloClient } from './api/apollo'
|
|
|
|
import App from './ui/App.vue'
|
|
import routes from './routes'
|
|
import storeOptions from './store'
|
|
|
|
function createApp (context) {
|
|
const router = new VueRouter({
|
|
mode: 'history',
|
|
routes,
|
|
})
|
|
|
|
const store = new Vuex.Store(storeOptions)
|
|
|
|
// sync the router with the vuex store.
|
|
// this registers `store.state.route`
|
|
sync(store, router)
|
|
|
|
// Apollo
|
|
const apolloClient = createApolloClient(context.ssr)
|
|
const apolloProvider = new VueApollo({
|
|
defaultClient: apolloClient,
|
|
})
|
|
|
|
return {
|
|
app: new Vue({
|
|
el: '#app',
|
|
router,
|
|
store,
|
|
provide: apolloProvider.provide(),
|
|
...App,
|
|
}),
|
|
router,
|
|
store,
|
|
apolloProvider,
|
|
}
|
|
}
|
|
|
|
export default createApp
|
|
```
|
|
|
|
On the client:
|
|
|
|
```javascript
|
|
import CreateApp from './app'
|
|
|
|
CreateApp({
|
|
ssr: false,
|
|
})
|
|
```
|
|
|
|
On the server:
|
|
|
|
```javascript
|
|
import { CreateApp } from './app'
|
|
|
|
const { app, router, store, apolloProvider } = CreateApp({
|
|
ssr: true,
|
|
})
|
|
|
|
// set router's location
|
|
router.push(context.url)
|
|
|
|
// wait until router has resolved possible async hooks
|
|
router.onReady(() => {
|
|
// Prefetch, render HTML (see above)
|
|
})
|
|
```
|
|
|
|
## Local state
|
|
|
|
If you need to manage local data, you can do so with [apollo-link-state](https://github.com/apollographql/apollo-link-state) and the `@client` directive:
|
|
|
|
```js
|
|
export default {
|
|
apollo: {
|
|
hello: gql`
|
|
query {
|
|
hello @client {
|
|
msg
|
|
}
|
|
}
|
|
`
|
|
},
|
|
mounted() {
|
|
// mutate the hello message
|
|
this.$apollo
|
|
.mutate({
|
|
mutation: gql`
|
|
mutation($msg: String!) {
|
|
updateHello(message: $msg) @client
|
|
}
|
|
`,
|
|
variables: {
|
|
msg: 'hello from link-state!'
|
|
}
|
|
})
|
|
}
|
|
}
|
|
```
|
|
|
|
[Example project](https://codesandbox.io/s/zqqj82396p) (thx @chriswingler)
|
|
|
|
---
|
|
|
|
# Migration
|
|
|
|
## Migrating from vue-apollo 2.x and apollo 1.x
|
|
|
|
The main changes are related to the apollo client setup. Your components code shouldn't be affected. Apollo now uses a more flexible [apollo-link](https://github.com/apollographql/apollo-link) system that allows compositing multiple links together to add more features (like batching, offline support and more).
|
|
|
|
### Installation
|
|
|
|
#### Packages
|
|
|
|
Before:
|
|
|
|
```
|
|
npm install --save vue-apollo apollo-client
|
|
```
|
|
|
|
After:
|
|
|
|
```
|
|
npm install --save vue-apollo@next graphql apollo-client apollo-link apollo-link-http apollo-cache-inmemory graphql-tag
|
|
```
|
|
|
|
#### Imports
|
|
|
|
Before:
|
|
|
|
```js
|
|
import Vue from 'vue'
|
|
import { ApolloClient, createBatchingNetworkInterface } from 'apollo-client'
|
|
import VueApollo from 'vue-apollo'
|
|
```
|
|
|
|
After:
|
|
|
|
```js
|
|
import Vue from 'vue'
|
|
import { ApolloClient } from 'apollo-client'
|
|
import { HttpLink } from 'apollo-link-http'
|
|
import { InMemoryCache } from 'apollo-cache-inmemory'
|
|
import VueApollo from 'vue-apollo'
|
|
```
|
|
|
|
#### Plugin Setup
|
|
|
|
Before:
|
|
|
|
```js
|
|
// Create the apollo client
|
|
const apolloClient = new ApolloClient({
|
|
networkInterface: createBatchingNetworkInterface({
|
|
uri: 'http://localhost:3020/graphql',
|
|
}),
|
|
connectToDevTools: true,
|
|
})
|
|
|
|
// Install the vue plugin
|
|
Vue.use(VueApollo)
|
|
```
|
|
|
|
After:
|
|
|
|
```js
|
|
const httpLink = new HttpLink({
|
|
// You should use an absolute URL here
|
|
uri: 'http://localhost:3020/graphql',
|
|
})
|
|
|
|
// Create the apollo client
|
|
const apolloClient = new ApolloClient({
|
|
link: httpLink,
|
|
cache: new InMemoryCache(),
|
|
connectToDevTools: true,
|
|
})
|
|
|
|
// Install the vue plugin
|
|
Vue.use(VueApollo)
|
|
```
|
|
|
|
### Mutations
|
|
|
|
Query reducers have been removed. Use the `update` API to update the cache now.
|
|
|
|
### Subscriptions
|
|
|
|
#### Packages
|
|
|
|
Before:
|
|
|
|
```
|
|
npm install --save subscriptions-transport-ws
|
|
```
|
|
|
|
After:
|
|
|
|
```
|
|
npm install --save apollo-link-ws apollo-utilities
|
|
```
|
|
|
|
#### Imports
|
|
|
|
Before:
|
|
|
|
```js
|
|
import { SubscriptionClient, addGraphQLSubscriptions } from 'subscriptions-transport-ws'
|
|
```
|
|
|
|
After:
|
|
|
|
```js
|
|
import { split } from 'apollo-link'
|
|
import { WebSocketLink } from 'apollo-link-ws'
|
|
import { getMainDefinition } from 'apollo-utilities'
|
|
```
|
|
|
|
#### Apollo Setup
|
|
|
|
Before:
|
|
|
|
```js
|
|
// Create the network interface
|
|
const networkInterface = createNetworkInterface({
|
|
uri: 'http://localhost:3000/graphql',
|
|
transportBatching: true,
|
|
})
|
|
|
|
// Create the subscription websocket client
|
|
const wsClient = new SubscriptionClient('ws://localhost:3000/subscriptions', {
|
|
reconnect: true,
|
|
})
|
|
|
|
// Extend the network interface with the subscription client
|
|
const networkInterfaceWithSubscriptions = addGraphQLSubscriptions(
|
|
networkInterface,
|
|
wsClient,
|
|
)
|
|
|
|
// Create the apollo client with the new network interface
|
|
const apolloClient = new ApolloClient({
|
|
networkInterface: networkInterfaceWithSubscriptions,
|
|
connectToDevTools: true,
|
|
})
|
|
|
|
// Install the plugin like before
|
|
Vue.use(VueApollo)
|
|
```
|
|
|
|
After:
|
|
|
|
```js
|
|
const httpLink = new HttpLink({
|
|
// You should use an absolute URL here
|
|
uri: 'http://localhost:3020/graphql',
|
|
})
|
|
|
|
// Create the subscription websocket link
|
|
const wsLink = new WebSocketLink({
|
|
uri: 'ws://localhost:3000/subscriptions',
|
|
options: {
|
|
reconnect: true,
|
|
},
|
|
})
|
|
|
|
// using the ability to split links, you can send data to each link
|
|
// depending on what kind of operation is being sent
|
|
const link = split(
|
|
// split based on operation type
|
|
({ query }) => {
|
|
const { kind, operation } = getMainDefinition(query)
|
|
return kind === 'OperationDefinition' &&
|
|
operation === 'subscription'
|
|
},
|
|
wsLink,
|
|
httpLink
|
|
)
|
|
|
|
// Create the apollo client
|
|
const apolloClient = new ApolloClient({
|
|
link,
|
|
cache: new InMemoryCache(),
|
|
connectToDevTools: true,
|
|
})
|
|
|
|
// Install the vue plugin like before
|
|
Vue.use(VueApollo)
|
|
```
|
|
|
|
|
|
<br>
|
|
|
|
Learn more at the [official apollo documentation](https://www.apollographql.com/docs/react/2.0-migration.html).
|
|
|
|
---
|
|
|
|
# API Reference
|
|
|
|
WIP (PR welcome!)
|
|
|
|
## ApolloProvider
|
|
|
|
### Constructor
|
|
|
|
```javascript
|
|
const apolloProvider = new VueApollo({
|
|
// Multiple clients support
|
|
// Use the 'client' option inside queries
|
|
// or '$client' on the apollo definition
|
|
clients: {
|
|
a: apolloClientA,
|
|
b: apolloClientB,
|
|
},
|
|
// Default client
|
|
defaultClient: apolloClient,
|
|
// Default 'apollo' definition
|
|
defaultOptions: {
|
|
// See 'apollo' definition
|
|
// For example: default loading key
|
|
$loadingKey: 'loading',
|
|
},
|
|
// Watch loading state for all queries
|
|
// See the 'watchLoading' advanced option
|
|
watchLoading (state, mod) {
|
|
loading += mod
|
|
console.log('Global loading', loading, mod)
|
|
},
|
|
// Global error handler for all smart queries and subscriptions
|
|
errorHandler (error) {
|
|
console.log('Global error handler')
|
|
console.error(error)
|
|
},
|
|
})
|
|
```
|
|
|
|
Use the apollo provider into your Vue app:
|
|
|
|
```javascript
|
|
new Vue({
|
|
el: '#app',
|
|
apolloProvider,
|
|
render: h => h(App),
|
|
})
|
|
```
|
|
|
|
### Methods
|
|
|
|
#### prefetchAll
|
|
|
|
(SSR) Prefetch all queued component definitions and returns a promise resolved when all corresponding apollo data is ready.
|
|
|
|
```javascript
|
|
await apolloProvider.prefetchAll (context, componentDefs, options)
|
|
```
|
|
|
|
`context` is passed as the argument to the `prefetch` options inside the smart queries. It may contain the route and the store.
|
|
|
|
`options` defaults to:
|
|
|
|
```javascript
|
|
{
|
|
// Include components outside of the routes
|
|
// that are registered with `willPrefetch`
|
|
includeGlobal: true,
|
|
}
|
|
```
|
|
|
|
#### getStates
|
|
|
|
(SSR) Returns the apollo stores states as JavaScript objects.
|
|
|
|
```JavaScript
|
|
const states = apolloProvider.getStates(options)
|
|
```
|
|
|
|
`options` defaults to:
|
|
|
|
```javascript
|
|
{
|
|
// Prefix for the keys of each apollo client state
|
|
exportNamespace: '',
|
|
}
|
|
```
|
|
|
|
#### exportStates
|
|
|
|
(SSR) Returns the apollo stores states as JavaScript code inside a String. This code can be directly injected to the page HTML inside a `<script>` tag.
|
|
|
|
```javascript
|
|
const js = apolloProvider.exportStates(options)
|
|
```
|
|
|
|
`options` defaults to:
|
|
|
|
```javascript
|
|
{
|
|
// Global variable name
|
|
globalName: '__APOLLO_STATE__',
|
|
// Global object on which the variable is set
|
|
attachTo: 'window',
|
|
// Prefix for the keys of each apollo client state
|
|
exportNamespace: '',
|
|
}
|
|
```
|
|
|
|
## Dollar Apollo
|
|
|
|
This is the apollo manager added to any component that uses apollo. It can be accessed inside a component with `this.$apollo`.
|
|
|
|
## Smart Query
|
|
|
|
Each query declared in the `apollo` definition (that is, which doesn't start with a `$` char) in a component results in the creation of a smart query object.
|
|
|
|
## Smart Subscription
|
|
|
|
Each subscription declared in the `apollo.$subscribe` option in a component results in the creation of a smart subscription object.
|
|
|
|
---
|
|
|
|
LICENCE ISC - Created by Guillaume CHAU (@Akryum)
|