Apollo in Vue
Integrates apollo in your vue components with declarative queries.
The Apollo project is still in development.
TODO npm package for use in non-meteor project
Installation
meteor add akryum:vue-apollo
Usage
Configuration
By default, the URL used to connect to the GraphQL server is /graphql
. Set the environment variable APOLLO_CLIENT_URL
to change it.
An ApolloClient
instance will be created internally when needed. You can add your own options to the meteorClientConfig
method (more in the apollo doc) and the ApolloClient
constructor (more in the apollo doc).
1import { VueApollo } from 'meteor/akryum:vue-apollo'; 2 3VueApollo.setMeteorClientConfig({ 4 path: '/graphql', 5 options: { ... }, 6 useMeteorAccounts: true 7}); 8 9VueApollo.setApolloClientOptions({ 10 shouldBatch: true 11});
You can access the ApolloClient
instance used by the package with the client
static property:
1console.log(VueApollo.client);
The first time this property is used an instance of ApolloClient
will be automatically created with the options you set. Note that if you change the options with setMeteorClientConfig()
or setApolloClientOptions()
afterwards, they won't be applied.
You can also tell the package to use your own ApolloClient
instance by setting it on the client
property before you start your Vue app:
1import ApolloClient from 'apollo-client'; 2 3VueApollo.client = new ApolloClient(options);
Usage in components
To declare apollo queries in your Vue component, add an apollo
object :
1new Vue({ 2 apollo: { 3 // Apollo specific options 4 } 5});
You can access the apollo-client instance with this.$apollo.client
in all your vue components.
In your apollo server resolvers, you can access the current Meteor user like this:
1user(root, args, context) { 2 // Only return data if the fetched id matches the current user, for security 3 if (context.user._id === args.id) { 4 return context.user; 5 } 6}
Queries
In the data
object, add an attribute for each property you want to feed with the result of an Apollo query.
Simple query
Put the gql query directly as the value:
1apollo: { 2 // Non-reactive query 3 data: { 4 // Simple query that will update the 'hello' vue property 5 hello: gql`{hello}` 6 } 7}
You don't need to call registerGqlTag
, it's already done by the package so you can use gql
everywhere in your app.
Don't forget to initialize your property in your vue component:
1data () { 2 return { 3 // Initialize your apollo data 4 hello: '' 5 } 6}
Server-side, add the corresponding schema and resolver:
1export const schema = ` 2type Query { 3 hello: String 4} 5 6schema { 7 query: Query 8} 9`; 10 11export const resolvers = { 12 Query: { 13 hello(root, args, context) { 14 return "Hello world!"; 15 } 16 } 17};
For more info, visit the apollo doc.
You can then use your property as usual in your vue component:
1<template> 2 <div class="apollo"> 3 <h3>Hello</h3> 4 <p> 5 {{hello}} 6 </p> 7 </div> 8</template>
Query with parameters
You can add variables (read parameters) to your gql
query by declaring query
and variables
in an object:
1// Apollo-specific options 2apollo: { 3 // Non-reactive query 4 data: { 5 // Query with parameters 6 ping: { 7 // gql query 8 query: gql`query PingMessage($message: String!) { 9 ping(message: $message) 10 }`, 11 // Static parameters 12 variables: { 13 message: 'Meow' 14 } 15 } 16 } 17}
You can use the following apollo options in the object:
forceFetch
fragments
See the apollo doc for more details.
For example, you could add the forceFetch
apollo option like this:
1apollo: { 2 data: { 3 // Query with parameters 4 ping: { 5 query: gql`query PingMessage($message: String!) { 6 ping(message: $message) 7 }`, 8 variables: { 9 message: 'Meow' 10 }, 11 // Additional options here 12 forceFetch: true 13 } 14 } 15}
Don't forget to initialize your property in your vue component:
1data () { 2 return { 3 // Initialize your apollo data 4 ping: '' 5 } 6}
Server-side, add the corresponding schema and resolver:
1export const schema = ` 2type Query { 3 ping(message: String!): String 4} 5 6schema { 7 query: Query 8} 9`; 10 11export const resolvers = { 12 Query: { 13 ping(root, { message }, context) { 14 return `Answering ${message}`; 15 } 16 } 17};
And then use it in your vue component:
1<template> 2 <div class="apollo"> 3 <h3>Ping</h3> 4 <p> 5 {{ping}} 6 </p> 7 </div> 8</template>
Reactive parameters
Use a function instead to make the parameters reactive with vue properties:
1// Apollo-specific options 2apollo: { 3 // Non-reactive query 4 data: { 5 // Query with parameters 6 ping: { 7 query: gql`query PingMessage($message: String!) { 8 ping(message: $message) 9 }`, 10 // Reactive parameters 11 variables() { 12 // Use vue reactive properties here 13 return { 14 message: this.pingInput 15 } 16 } 17 } 18 } 19}
This will re-fetch the query each time a parameter changes, for example:
1<template> 2 <div class="apollo"> 3 <h3>Ping</h3> 4 <input v-model="pingInput" placeholder="Enter a message" /> 5 <p> 6 {{ping}} 7 </p> 8 </div> 9</template>
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 matchresult(data)
is a hook called when a result is receivederror(errors, type)
is a hook called when there are errors,type
value can either be'sending'
or'execution'
loadingKey
will update the component data property you pass as the value. You should initialize this property to0
in the componentdata()
hook. When the query is loading, this property will be incremented by 1 and as soon as it no longer is, the property 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. ThecountModifier
parameter is either equal to1
when the query is now loading, or-1
when the query is no longer loading.
1// Apollo-specific options 2apollo: { 3 // Non-reactive query 4 data: { 5 // Advanced query with parameters 6 // The 'variables' method is watched by vue 7 pingMessage: { 8 query: gql`query PingMessage($message: String!) { 9 ping(message: $message) 10 }`, 11 // Reactive parameters 12 variables() { 13 // Use vue reactive properties here 14 return { 15 message: this.pingInput 16 } 17 }, 18 // We use a custom update callback because 19 // the field names don't match 20 // By default, the 'pingMessage' attribute 21 // would be used on the 'data' result object 22 // Here we know the result is in the 'ping' attribute 23 // considering the way the apollo server works 24 update(data) { 25 console.log(data); 26 // The returned value will update 27 // the vue property 'pingMessage' 28 return data.ping; 29 }, 30 // Optional result hook 31 result(data) { 32 console.log("We got some result!"); 33 }, 34 // Error handling 35 error(errors, type) { 36 console.error(`We've got ${errors.length} errors of type '${type}'`); 37 }, 38 // Loading state 39 // loadingKey is the name of the data property 40 // that will be incremented when the query is loading 41 // and decremented when it no longer is. 42 loadingKey: 'loadingQueriesCount', 43 // watchLoading will be called whenever the loading state changes 44 watchLoading(isLoading, countModifier) { 45 // isLoading is a boolean 46 // countModifier is either 1 or -1 47 } 48 } 49 } 50}
If you use ES2015, you can also write the update
like this:
1update: data => data.ping
Reactive Queries
For now, the reactivity in apollo is quite limited, since you can only do polling.
For more info, see the apollo doc.
Add your queries in a watch
object instead of data
:
1// Apollo-specific options 2apollo: { 3 // Reactive query 4 watch: { 5 // 'tags' data property on vue instance 6 tags: { 7 query: gql`{ 8 tags { 9 id, 10 label 11 } 12 }`, 13 pollInterval: 300 // ms 14 } 15 } 16}
You can use the following apollo options:
forceFetch
returnPartialData
pollInterval
fragments
See the apollo doc for more details.
You can also use the advanced options detailed above, like result
or watchLoading
.
Here is how the server-side looks like:
1export const schema = ` 2type Tag { 3 id: Int 4 label: String 5} 6 7type Query { 8 tags: [Tag] 9} 10 11schema { 12 query: Query 13} 14`; 15 16// Fake word generator 17import casual from 'casual'; 18 19// Let's generate some tags 20var id = 0; 21var tags = []; 22for (let i = 0; i < 42; i++) { 23 addTag(casual.word); 24} 25 26function addTag(label) { 27 let t = { 28 id: id++, 29 label 30 }; 31 tags.push(t); 32 return t; 33} 34 35export const resolvers = { 36 Query: { 37 tags(root, args, context) { 38 return tags; 39 } 40 } 41};
Mutations
Mutations are queries that changes your data state on your apollo server. For more info, visit the apollo doc.
1methods: { 2 addTag() { 3 // Mutate the tags data 4 // You can also use this.$apollo.client.mutate 5 this.$apollo.mutate({ 6 mutation: gql`mutation AddTag($label: String!) { 7 addTag(label: $label) { 8 id, 9 label 10 } 11 }`, 12 // Parameters 13 variables: { 14 label: this.tagLabel 15 } 16 }).then((data) => { 17 // Result 18 console.log(data); 19 this.tagLabel = ''; 20 }).catch((error) => { 21 // Error 22 console.error(error); 23 }); 24 } 25}
Server-side:
1export const schema = ` 2type Tag { 3 id: Int 4 label: String 5} 6 7type Query { 8 tags: [Tag] 9} 10 11type Mutation { 12 addTag(label: String!): Tag 13} 14 15schema { 16 query: Query 17 mutation: Mutation 18} 19`; 20 21// Fake word generator 22import faker from 'faker'; 23 24// Let's generate some tags 25var id = 0; 26var tags = []; 27for (let i = 0; i < 42; i++) { 28 addTag(faker.random.word()); 29} 30 31function addTag(label) { 32 let t = { 33 id: id++, 34 label 35 }; 36 tags.push(t); 37 return t; 38} 39 40export const resolvers = { 41 Query: { 42 tags(root, args, context) { 43 return tags; 44 } 45 }, 46 Mutation: { 47 addTag(root, { label }, context) { 48 console.log(`adding tag '${label}'`); 49 return addTag(label); 50 } 51 } 52};
Next steps
- Add routing to your app
- Add internationalization to your app
- Manage your app state with a vuex store
LICENCE ISC - Created by Guillaume CHAU (@Akryum)