DEV Community

Scott Molinari for Quasar

Posted on • Edited on

Quasar and Apollo - Client State without Vuex - Part 2

Part 2 - Vue-Apollo and its Working Parts - Queries

If you've landed here inadvertently and haven't read the first part, please do.

This tutorial has 4 parts:

Apollo and Vue

Apollo is an awesome GraphQL client package and if you look at their docs, you'll notice it's mainly aimed at React. However, we in the world of Vue, thankfully, have a nice Vue plug-in package called vue-apollo, which helps offer a sleek API into Apollo within Vue (and of course Quasar).

The Flow of Data in GraphQL

Below is a diagram showing the flow of data through a Quasar/Vue/Apollo app.

If you are familiar with Vuex, the flow above might look somewhat familiar to you. With Apollo, you have Queries that pull the data from "a source" into the view, similar to Vuex's getters. And, you have mutations that alter the data in that same "source", similar to Vuex's setters.

Fact of the matter is, this is the magic of GraphQL. While with other solutions the devs are fumbling around with GET and POST requests to a REST API to get data and mutate data between the client and the server and trying to figure out the right reply signature to make things semi-efficient and also how to best get it into their store, GraphQL is already just doing its thing. As soon as the mutation or query is written (of course, with the server or client side resolvers having been developed too), you are getting the responses you expect, and more importantly, in the form you need them.

If you'll notice, in the middle of the diagram is a "store". That's Apollo's caching mechanism. Our litte todo app is only using that store and we won't be making calls to a server. However, if we did want data from or mutate it on the server, we wouldn't need to change our queries and mutations very much to make it happen. This will be explained better later.

For now, just realize the queries and mutations you are about to see are the same or similar for any GraphQL system or for calling from any "resource". That, in turn, is also a great benefit of GraphQL. It is is a specification and with a specification, you get standardization. And standardization means, less cognitive load for onboarding new devs, and that means better and faster solutioning.

If you are at all in the know about GraphQL, we probably didn't need to mention this. But, if you are new to GraphQL, welcome to a new paradigm of data fetching and mutation.

Priming The Cache (or Store)

Before we can begin, one thing is very important. If we are going to be addressing the Apollo Cache ourselves to use it as our single-source of truth, we need to "prime" it. It's almost like how Vue needs data to be initialized in a component, before it can be reactive.

In our example todo app, if you go to /src/quasar-app-extension-apollo/apollo-clent-hooks.js you'll see how this is done.

We've got our todos array, the networkStatus object and a filters object set up.

Again, this needs to happen, but only for the data we want to store within Apollo for client-side state.

Before we continue to dig in, if you don't have any or very little knowledge of GraphQL, you should learn a bit more about it. Otherwise, we might be losing you at some point below and we don't want to have that. "How to GraphQL" is a good tutorial to get started and learn about the basics of GraphQL. For now, you only need to learn about the client side concepts and the concept of resolvers on the server and what they mean in response to queries and mutations.

Queries and the Apollo Option

In our todo app, we only have two queries. One for the list of tasks, our "todos", and one for the list of filters of the todo list (All, Completed and Active).

The filters could very well have been hard-coded into the components, but we've done it this way to demonstrate two things:

  1. Another way to instantiate the cache with values you need (as shown above).
  2. To show that you could theoretically also pull the list of filters from the server too and not have them hard coded in your component.

By that last point, we mean that you could very well pull the data of what filters offer, like the text in them, from the server, making the filterBar very dynamic for say, i18n purposes.

Let's start with the query for filters.

Go to the FilterBar component. You'll see it is a normal component with a QBtn.

So what do we have here?

In...

  • lines 1-14 we have our <template> section, where we are building our possible filters via a v-for loop of the queried data.
  • line 17, we are importing our queries and mutations.
  • lines 19-26, we are initializing our component's data/ state.
  • lines 28-30, we are using the apollo option to inject our query into the component in order to get the list of filters from our store (more on this later).
  • lines 32-43, we have a method with our mutation (more on this in Part 3.)

Important to note is the apollo option. This is how we can "connect" our component's data to the data we are querying for. The property should always match a property within the component's data and vue-apollo will automatically assign the result object to the matching data property. This is my personal favorite way of adding queries. There is another couple of ways, one we'll cover below.

One more note, should you not want to match your data's naming, you can also assign the property in Apollo to your data property via the update option of Apollo. For instance, let's say our data prop wasn't filters, but rather fooFilters. You could do something like this.

1  apollo: {
2    filters: {
3      query: gql`query {
4        // our query would be here. 
5      }`,
6      update: data => data.fooFilters
7    }
8  }
Enter fullscreen mode Exit fullscreen mode

Getting back to the query, if you open up our queries.js file under the /graphql/Todos folder, it looks like this.

1  export const getFilters = gql`
2    query GetFilters {
3      filters @client {
4        name
5        label
6        active
7      }
8  }
9  `
10
Enter fullscreen mode Exit fullscreen mode

It's a very simple GraphQL query.

Now let's go one up in the hierarchy and look at our TodoList.vue file.

So what do we have here?

In...

  • lines 1-10, we have our v-for loop building our todo list of tasks.
  • lines 13-14, we are importing our queries and components.
  • lines 16-25, we intializing our component's data.
  • lines 27-30, we are injecting our queries into our apollo option.
  • lines 32-45, we have a computed method to calculate our "visible" todos.
  • lines 47-58, we have a watcher on our todos, so when they are all "completed", we give the user a nice motivational message.
  • lines 60-65, we have another watcher watching the filters data and setting the active filter accordingly.

If you've noticed, we are, once again, querying for the filters. This is so we can filter the todo list on the active filter.

Here is a challenge. Can you imagine a different way of doing this? If yes, explain what your method would be in the comments below!

So that is how we query in vue-apollo. However, that is only one way. There are others....

The Vue-Apollo <ApolloQuery> Component

Another way to do an Apollo query is via the vue-apollo query component. If you look at the /components folder, there is an alternate TodoList.vue file called TodoList.alt.vue.

Open it up and you'll see this.

So what is different here?

In...

  • lines 2-12, we have the <ApolloQuery> component.
  • lines 40-42, we are only injecting the filters query, because our todos query is above in the template now.
  • line 44, we changed our computed to a method. Theoretically, it could have stayed a computed, but it looks a bit cleaner this way.
  • line 59, we had to change our watcher to watch all data in the <ApolloQuery> component.

There are two other methods to get the query into the component.

  1. By assigning the query to a data prop.
1  <template>
2    <ApolloQuery
3      :query="todoQuery" //<--here
4    >
....
5    </ApolloQuery>
6  </template>
7
8  <script>
9  import { queries } from 'src/graphql/Todos'
10 import Todo from 'components/Todo'
11
12 export default {
13   name: 'TodosList',
14   components: { Todo },
15   data () {
16     return {
17       filters: [],
18       activeFilter: ''
19       todoQuery: queries.getTodos //<--our query
20     }
21   },
22
Enter fullscreen mode Exit fullscreen mode

Using '.gql' Files

Using 'gql' or 'graphql' files has a slight advantage over putting the code in JavaScript, as we've been doing. With them in their own files, we can take advantage of Webpack and the GraphQL AST (your tree of queries) doesn't get built on the client, but rather on the server. As your application gets larger, you might want to consider this approach to save compute time on the client.

This is what using a .gql file might look like.

1  <template>
2    <ApolloQuery
3      :query="require('../graphql/GetTodos.gql')"
4    >
....
5    </ApolloQuery>
6  </template>
Enter fullscreen mode Exit fullscreen mode

In order for this to work, however, you must add the appropriate Webpack loader into quasar.conf.js. You'll need to add this module rule.

chain.module.rule('gql')
   .test(/\.(graphql|gql)$/)
   .use('graphql-tag/loader')
   .loader('graphql-tag/loader')
Enter fullscreen mode Exit fullscreen mode

Example code was added to the codesandbox to the app repo. See TodoList.alt2.vue and the quasar.conf.js files.

Conclusion

So, this is how you can query for data within Quasar/ Vue with the vue-apollo package and with Apollo Client. There is more advanced things to learn about vue-apollo and querying, like how to offer variables with the query and more. You can find out more in the resources below.

Resources:

vue-apollo docs: https://apollo.vuejs.org/guide/
GraphQL docs: https://graphql.org/learn/
Apollo Client docs: https://www.apollographql.com/docs/react/

In Part 3 we'll be going over mutations and closing the loop between pulling data and relating it to our components and also manipulating that same data and seeing the manipulations happening.

Top comments (1)

Collapse
 
wyepez profile image
Washington Yepez

"Here is a challenge. Can you imagine a different way of doing this? If yes, explain what your method would be in the comments below!" --> This is exactly the kind of troubles that I'm facing. I have a MenuBar component with a refresh button and another component with a table bound to a smart query. With vuex, I bound the table with the data from a getter in the store and from the menu bar, when the user click on the refresh button, an action is called on the store to update the data that is bound in the table. But with apollo using its cache, I found it very difficult to update a smart query from another component. I had to propagate events up and write props down to get this which it's not nice if I used to use vuex to not do it.