Initial version
This commit is contained in:
@@ -0,0 +1 @@
|
||||
node_modules/
|
||||
@@ -1,2 +1,487 @@
|
||||
# vue-apollo
|
||||
Vue apollo integration
|
||||
# Apollo in Vue
|
||||
|
||||
Integrates [apollo](http://www.apollostack.com/) in your vue components with declarative queries.
|
||||
|
||||
## Installation
|
||||
|
||||
|
||||
npm install vue-apollo
|
||||
|
||||
## Usage
|
||||
|
||||
### Configuration
|
||||
|
||||
```javascript
|
||||
import Vue from 'vue';
|
||||
import ApolloClient from 'apollo-client';
|
||||
import VueApollo from 'vue-apollo'
|
||||
|
||||
const apolloClient = new ApolloClient({
|
||||
/* ... */
|
||||
});
|
||||
|
||||
Vue.use(VueApollo, {
|
||||
apolloClient,
|
||||
});
|
||||
```
|
||||
|
||||
### 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](http://docs.apollostack.com/apollo-client/index.html) instance with `this.$apollo.client` in all your vue components.
|
||||
|
||||
### 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](http://docs.apollostack.com/apollo-client/core.html#gql) query directly as the value:
|
||||
|
||||
```javascript
|
||||
apollo: {
|
||||
// Non-reactive query
|
||||
data: {
|
||||
// Simple query that will update the 'hello' vue property
|
||||
hello: gql`{hello}`
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
Don't forget to initialize your property in your vue component:
|
||||
|
||||
```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](http://docs.apollostack.com/apollo-server/index.html).
|
||||
|
||||
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: {
|
||||
// Non-reactive query
|
||||
data: {
|
||||
// Query with parameters
|
||||
ping: {
|
||||
// gql query
|
||||
query: gql`query PingMessage($message: String!) {
|
||||
ping(message: $message)
|
||||
}`,
|
||||
// Static parameters
|
||||
variables: {
|
||||
message: 'Meow'
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
You can use the following apollo options in the object:
|
||||
- `forceFetch`
|
||||
- `fragments`
|
||||
|
||||
See the [apollo doc](http://docs.apollostack.com/apollo-client/queries.html#query) for more details.
|
||||
|
||||
For example, you could add the `forceFetch` apollo option like this:
|
||||
|
||||
```javascript
|
||||
apollo: {
|
||||
data: {
|
||||
// Query with parameters
|
||||
ping: {
|
||||
query: gql`query PingMessage($message: String!) {
|
||||
ping(message: $message)
|
||||
}`,
|
||||
variables: {
|
||||
message: 'Meow'
|
||||
},
|
||||
// Additional options here
|
||||
forceFetch: true
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
Don't forget to 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>
|
||||
```
|
||||
|
||||
#### Reactive parameters
|
||||
|
||||
Use a function instead to make the parameters reactive with vue properties:
|
||||
|
||||
```javascript
|
||||
// Apollo-specific options
|
||||
apollo: {
|
||||
// Non-reactive query
|
||||
data: {
|
||||
// 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>
|
||||
```
|
||||
|
||||
#### 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(data)` is a hook called when a result is received
|
||||
- `error(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 to `0` in the component `data()` 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. The `countModifier` parameter is either equal to `1` when the query is now loading, or `-1` when the query is no longer loading.
|
||||
|
||||
|
||||
```javascript
|
||||
// Apollo-specific options
|
||||
apollo: {
|
||||
// Non-reactive query
|
||||
data: {
|
||||
// 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
|
||||
}
|
||||
},
|
||||
// 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) {
|
||||
console.log("We got some result!");
|
||||
},
|
||||
// Error handling
|
||||
error(errors, type) {
|
||||
console.error(`We've got ${errors.length} errors of type '${type}'`);
|
||||
},
|
||||
// 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
|
||||
```
|
||||
|
||||
### Reactive Queries
|
||||
|
||||
*For now, the reactivity in apollo is quite limited, since you can only do polling.*
|
||||
|
||||
For more info, see the [apollo doc](http://docs.apollostack.com/apollo-client/core.html#watchQuery).
|
||||
|
||||
Add your queries in a `watch` object instead of `data`:
|
||||
|
||||
```javascript
|
||||
// Apollo-specific options
|
||||
apollo: {
|
||||
// Reactive query
|
||||
watch: {
|
||||
// 'tags' data property on vue instance
|
||||
tags: {
|
||||
query: gql`{
|
||||
tags {
|
||||
id,
|
||||
label
|
||||
}
|
||||
}`,
|
||||
pollInterval: 300 // ms
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
You can use the apollo options, for example:
|
||||
- `forceFetch`
|
||||
- `returnPartialData`
|
||||
- `pollInterval`
|
||||
- `fragments`
|
||||
|
||||
See the [apollo doc](http://docs.apollostack.com/apollo-client/queries.html#watchQuery) for more details.
|
||||
|
||||
You can also use the advanced options detailed above, like `result` or `watchLoading`.
|
||||
|
||||
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;
|
||||
}
|
||||
}
|
||||
};
|
||||
```
|
||||
|
||||
### Mutations
|
||||
|
||||
Mutations are queries that changes your data state on your apollo server. For more info, visit the [apollo doc](http://docs.apollostack.com/apollo-client/core.html#Mutations).
|
||||
|
||||
```javascript
|
||||
methods: {
|
||||
addTag() {
|
||||
// Mutate the tags data
|
||||
// You can also use this.$apollo.client.mutate
|
||||
this.$apollo.mutate({
|
||||
mutation: gql`mutation AddTag($label: String!) {
|
||||
addTag(label: $label) {
|
||||
id,
|
||||
label
|
||||
}
|
||||
}`,
|
||||
// Parameters
|
||||
variables: {
|
||||
label: this.tagLabel
|
||||
}
|
||||
}).then((data) => {
|
||||
// Result
|
||||
console.log(data);
|
||||
this.tagLabel = '';
|
||||
}).catch((error) => {
|
||||
// Error
|
||||
console.error(error);
|
||||
});
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
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);
|
||||
}
|
||||
}
|
||||
};
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
LICENCE ISC - Created by Guillaume CHAU (@Akryum)
|
||||
|
||||
@@ -0,0 +1,2 @@
|
||||
import VueApollo from './lib/vue-plugin';
|
||||
export default VueApollo;
|
||||
@@ -0,0 +1,230 @@
|
||||
import _ from 'lodash';
|
||||
|
||||
let apolloClient = null;
|
||||
|
||||
class DollarApollo {
|
||||
constructor(vm) {
|
||||
this.vm = vm;
|
||||
this.querySubscriptions = {};
|
||||
}
|
||||
|
||||
get client() {
|
||||
return apolloClient;
|
||||
}
|
||||
|
||||
get query() {
|
||||
return this.client.query;
|
||||
}
|
||||
|
||||
watchQuery(options) {
|
||||
const vm = this.vm;
|
||||
const observable = this.client.watchQuery(options);
|
||||
const _subscribe = observable.subscribe.bind(observable);
|
||||
observable.subscribe = (function(options) {
|
||||
let sub = _subscribe(options);
|
||||
vm._apolloSubscriptions.push(sub);
|
||||
return sub;
|
||||
}).bind(observable);
|
||||
return observable;
|
||||
}
|
||||
|
||||
get mutate() {
|
||||
return this.client.mutate;
|
||||
}
|
||||
|
||||
option(key, options, watch) {
|
||||
const vm = this.vm;
|
||||
const $apollo = this;
|
||||
|
||||
let query, observer, sub;
|
||||
let simpleQuery = false;
|
||||
|
||||
let firstLoadingDone = false;
|
||||
|
||||
let loadingKey = options.loadingKey;
|
||||
let loadingChangeCb = options.watchLoading;
|
||||
|
||||
if (options.pollInterval) {
|
||||
watch = true;
|
||||
}
|
||||
|
||||
if (typeof loadingChangeCb === 'function') {
|
||||
loadingChangeCb = loadingChangeCb.bind(vm);
|
||||
}
|
||||
|
||||
// Simple query
|
||||
if (!options.query) {
|
||||
query = options;
|
||||
simpleQuery = true;
|
||||
}
|
||||
|
||||
function generateApolloOptions(variables) {
|
||||
const apolloOptions = _.omit(options, [
|
||||
'variables',
|
||||
'watch',
|
||||
'update',
|
||||
'result',
|
||||
'error',
|
||||
'loadingKey',
|
||||
'watchLoading',
|
||||
]);
|
||||
apolloOptions.variables = variables;
|
||||
return apolloOptions;
|
||||
}
|
||||
|
||||
function q(variables) {
|
||||
applyLoadingModifier(1);
|
||||
|
||||
if (simpleQuery) {
|
||||
|
||||
$apollo.query({
|
||||
query
|
||||
}).then(nextResult).catch(catchError);
|
||||
|
||||
} else if (watch) {
|
||||
|
||||
if (options.forceFetch && observer) {
|
||||
// Refresh query
|
||||
observer.refetch(variables, {
|
||||
forceFetch: !!options.forceFetch
|
||||
});
|
||||
} else {
|
||||
if (sub) {
|
||||
sub.unsubscribe();
|
||||
}
|
||||
|
||||
// Create observer
|
||||
observer = $apollo.watchQuery(generateApolloOptions(variables));
|
||||
|
||||
// Create subscription
|
||||
sub = observer.subscribe({
|
||||
next: nextResult,
|
||||
error: catchError
|
||||
});
|
||||
}
|
||||
|
||||
} else {
|
||||
|
||||
$apollo.query(generateApolloOptions(variables)).then(nextResult).catch(catchError);
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
if (typeof options.variables === 'function') {
|
||||
vm.$watch(options.variables.bind(vm), q, {
|
||||
immediate: true
|
||||
});
|
||||
} else {
|
||||
q(options.variables);
|
||||
}
|
||||
|
||||
function nextResult({ data }) {
|
||||
applyData(data);
|
||||
}
|
||||
|
||||
function applyData(data) {
|
||||
loadingDone();
|
||||
|
||||
console.log(data, key);
|
||||
|
||||
if (typeof options.update === 'function') {
|
||||
vm[key] = options.update.call(vm, data);
|
||||
} else if (data[key] === undefined) {
|
||||
console.error(`Missing ${key} attribute on result`, data);
|
||||
} else {
|
||||
vm[key] = data[key];
|
||||
}
|
||||
|
||||
if (typeof options.result === 'function') {
|
||||
options.result.call(vm, data);
|
||||
}
|
||||
}
|
||||
|
||||
function applyLoadingModifier(value) {
|
||||
if (loadingKey) {
|
||||
vm.$set(loadingKey, vm.$get(loadingKey) + value);
|
||||
}
|
||||
|
||||
if (loadingChangeCb) {
|
||||
loadingChangeCb(value === 1, value);
|
||||
}
|
||||
}
|
||||
|
||||
function loadingDone() {
|
||||
if (!firstLoadingDone) {
|
||||
applyLoadingModifier(-1);
|
||||
firstLoadingDone = true;
|
||||
}
|
||||
}
|
||||
|
||||
function catchError(error) {
|
||||
loadingDone();
|
||||
|
||||
if (error.graphQLErrors && error.graphQLErrors.length !== 0) {
|
||||
console.error(`GraphQL execution errors for query ${query}`);
|
||||
for (let e of error.graphQLErrors) {
|
||||
console.error(e);
|
||||
}
|
||||
} else if (error.networkError) {
|
||||
console.error(`Error sending the query ${query}`, error.networkError);
|
||||
} else {
|
||||
console.error(error);
|
||||
}
|
||||
|
||||
if (typeof options.error === 'function') {
|
||||
options.error(error);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function prepare() {
|
||||
this._apolloSubscriptions = [];
|
||||
|
||||
this.$apollo = new DollarApollo(this);
|
||||
|
||||
let apollo = this.$options.apollo;
|
||||
|
||||
if (apollo) {
|
||||
// One-time queries with $query(), called each time a Vue dependency is updated (using $watch)
|
||||
if (apollo.data) {
|
||||
for (let key in apollo.data) {
|
||||
this.$apollo.option(key, apollo.data[key], false);
|
||||
}
|
||||
}
|
||||
|
||||
// Auto updating queries with $watchQuery(), re-called each time a Vue dependency is updated (using $watch)
|
||||
if (apollo.watch) {
|
||||
for (let key in apollo.watch) {
|
||||
this.$apollo.option(key, apollo.watch[key], true);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export default {
|
||||
install(Vue, options) {
|
||||
|
||||
apolloClient = options.apolloClient;
|
||||
|
||||
Vue.mixin({
|
||||
|
||||
// Vue 1.x
|
||||
beforeCompile: prepare,
|
||||
// Vue 2.x
|
||||
beforeCreate: prepare,
|
||||
|
||||
destroyed: function() {
|
||||
this._apolloSubscriptions.forEach((sub) => {
|
||||
sub.unsubscribe();
|
||||
});
|
||||
this._apolloSubscriptions = null;
|
||||
if (this.$apollo) {
|
||||
this.$apollo = null;
|
||||
}
|
||||
}
|
||||
|
||||
});
|
||||
|
||||
}
|
||||
};
|
||||
@@ -0,0 +1,27 @@
|
||||
{
|
||||
"name": "vue-apollo",
|
||||
"version": "1.0.0",
|
||||
"description": "Vue apollo integration",
|
||||
"main": "index.js",
|
||||
"scripts": {
|
||||
"test": "echo \"Error: no test specified\" && exit 1"
|
||||
},
|
||||
"repository": {
|
||||
"type": "git",
|
||||
"url": "git+https://github.com/Akryum/vue-apollo.git"
|
||||
},
|
||||
"keywords": [
|
||||
"vue",
|
||||
"apollo",
|
||||
"graphql"
|
||||
],
|
||||
"author": "Guillaume Chau <guillaume.b.chau@gmail.com>",
|
||||
"license": "ISC",
|
||||
"bugs": {
|
||||
"url": "https://github.com/Akryum/vue-apollo/issues"
|
||||
},
|
||||
"homepage": "https://github.com/Akryum/vue-apollo#readme",
|
||||
"dependencies": {
|
||||
"lodash": "^4.15.0"
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user