Continuing with the series, let’s conclude with this final article. While the first post was focused on the modeling of the domain and second post focused on how to perform GraphQL queries with the backend, this will cover how I made it shine in the frontend with Vue 3 and Apollo.
Disclaimer: I’m not a UI/UX expert, but I did my best to keep the styling properly scoped and structured with my limited CSS skills =p
Vue, The Progressive JavaScript Framework
Vue is an open-source javascript framework for building web applications, released at the beginning of 2014. It’s a very straightforward, powerful, and progressive framework. I’ve been using it personally and professionally for a while and can certainly attest to how it allows me to produce very clean and well-structured applications with little effort.
It is currently in version 3.x
and has been improving with many nice features, proving that its community does solid work in maintaining and ensuring a good development experience.
For this project, I initially started it using the classic Options API. However, I fully refactored it to use the new Composition API and its native support for Typescript
. From what I’ve learned, this is the definitive way to use Vue.
The Apollo library is another crucial piece to make Vue communicate with our now up-and-running GraphQL server, allowing me to integrate GraphQL in this Vue app easily. For more advanced features, check their official documentation.
Components hierarchy
Keeping up the initial structure built by Vue CLI, I organized the Pokégraph with the following structure:
components
- MainDevice
- AttributesDisplay
- AvatarDisplay
- GenderRatioDisplay
- JoystickControl
- LegendaryDisplay
- LoadingSpinner
- StatsDisplay
- TypeDisplay
- SearchBox
- MainDevice
composables
graphql
models
App .vue
Before we start, notice how simple the code becomes with Composition API
. I got rid of all the setup()
, methods()
, watch()
in all components and all I needed to enable this simplicity was:
Now, going to App.vue, the application component entry point, I placed the child-components SearchBox
and MainDevice
in the template and you’ll see them all detailed.
Script
In the script section, I start running the useQuery
function from @vue/apollo-composable
to perform two queries defined here; the first to list all Pokemon and display the count of all discovered monsters; the second to search for one in specific, in which I set its number within the reactive const named variables
.
SearchBox
A fairly simple component to search Pokémon by number. Besides, it shows the Pokémon number and because it works via two-way binding, you can type the value in the field and press enter to trigger @keyup.enter
, calling a method that will emit a component event to the parent component. This technique allows us to make components with one another.
MainDevice
The primary device is a Pokédex itself. It’s effortless in logic because all the complexity was split into smaller components that use its prop that receives the ViewModel from the server and then passes it to the inner components. To make sense of it, I created the PokemonViewModel interface, so further on, I can cast the result into it for some components that need the clarity of a strongly typed object.
The template uses a static image of a Pokédex as the background of a div, with some clickable buttons well positioned within its joystick cross. When you click on each, you can goUp
, goLeft
, goRight
and goDown
. These methods will emit
the onPokemonNumberChanged
with a calculated Pokémon number to the App component, which will update its reactive variable, fetching the data from the server every time it changes. For emitting events throughout the application, I’m using a nice library called mitt.
If you read the first post of this series, you will notice the model structure that allows us to navigate traversal through a Pokémon evolution or pre-evolution path using the U1p
and Down
arrows respectively.
The MainDevice template section is composed of the following components:
- AttributesDisplay: It just a the
Height
andWeigth
of the Pokémon on the top left of the device display. - GenderRatioDisplay: Also positioned on the left side, this displays the proportion of males and females of the Pokémon species in a progress bar-like style.
- LegendaryDisplay: A complimentary label positioned above the avatar indicates when a Pokémon is legendary. Only for a few ^^
- AvatarDisplay: It displays the image of our Pokémon in a squared container, if any image was set. I used some Transitions to give a nice effect to it.
- TypeDisplay: As you probably know at this point, a Pokémon might be classified up to 2 types, so this component displays the badges for each type accordingly and with dynamic background colors listed in this PokemonTypes enum:
- JoystickControl: Invisible clickable buttons positioned right over the joystic cross of the device.
- StatsDisplay: Displays the Pokémon base stats on the right blue panel. It resembles a calculator and, indeed it does the sum of all stats into the total computed property.
Subscriptions
Composables
In the context of Vue applications, a “composable” is a function that leverages Vue’s Composition API to encapsulate and reuse stateful logic.
I placed them in the composables
folder. One for watching Apollo subscriptions, and another for using Vue Toast to pop-up fancy notifications. You can learn more about Composables here .
Back to the App.vue
and in the onMounted
lifecycle hook, I’m calling the composable watchSubscriptions()
method that will run useSubscription()
from @vue/apollo-composable
and watch
some messages comming from the server, which can be onListedPokemonResult
, onInsertedPokemonResult
or onUpdatedPokemonResult
. These messages are sent when performing querying or mutations.
To get all this working, I had to configure the ApolloClient
with the WebSocketLink
against a wss
uri. All this was set in the main.ts
file:
Final thoughts
Building this project was very fun for me. I learned more about GraphQL and its capabilities. I confirmed why it is so widely spread as an alternative to reduce endpoint complexity for fetching large amounts of data with flexibility.
Although I just populated the database with the first 151 pocket monsters, I’m sure it would Catch ‘Em All
effortlessly. On top of that, Vue shows once more that it’s a solid framework, ready to progress and supporting practically any technology, yet making everything look simple.
Thank you for reading until now. If it helped you and you liked the project, please don’t forget to give a ⭐ to the repository.