Building a PWA news aggregator using Vue.js and Vuex – Part 1: Setup

Inspired by a recent article found on Medium about create a news aggregator, I decided to recreate something for Vue.js, improving the HTML markup and design, convert it into a PWA and use the CSS Grid system.

Just like the article source, I used Reddit API to build the aggregator, since I found it interesting also for personal usage, still anything can be used as alternative.

You can see the final result in this link or try this tutorial in this sandbox.

Project setup

To start the project I’ve used the Vue/PWA template and published with Netlify. Since to build a PWA requires the use of a HTTPS protocol, I preferred Netlify to TravisCI this time, also to get all the other features including Continuous Deployment, CDN, one-click setup, free domain, etc.

Therefore we’ll start the project using vue-cli:

$ npm install -g vue-cli
$ vue init pwa my-project
$ cd my-project
$ npm install
$ npm run dev

We’ll need then to add Vuex to our project writing npm install vuex --D and then setup our structure tree as in documentation, adding store and API folders.

├── index.html
├── main.js
├── App.vue
├── api
│   └── ... # abstractions for making API requests
├── components
│   ├── Home.vue
│   └── ...
└── store
    ├── index.js          # where we assemble modules and export the store
    ├── actions.js        # root actions
    ├── mutations.js      # root mutations
    ├── getters.js        # root getters
    └── modules
        ├── cart.js       # cart module
        └── products.js   # products module

Now open your app.vue file and add both modules and store options.

import Vuex from 'vuex'
import Vuex from 'vuex'
import store from '@/store'

Vue.use(Vuex)

export default {
 name: 'app',
 store
}

In this way you can just load the entire store in your app once, so you’ll be able to call it through your components.

Now inside your store/index.js let’s add the store to export with the whole app. Please note how the state data will be defined here.

import Vuex from 'vuex'
import * as actions from './actions'
import * as getters from './getters'
import mutations from './mutations'

const store = () => {
 return new Vuex.Store({
   state: {
   },
   actions,
   mutations,
   getters
 })
}

export default store

Vuex settings

The first declaration to be done is obviously inside our store/index.js, where we are going to import all the options and declare our variables.

import Vuex from 'vuex'
import * as actions from './actions'
import * as getters from './getters'
import mutations from './mutations'

const store = () => {
 return new Vuex.Store({
  state: {
   posts: null,
   subreddits: ['technews', 'WorldNews', 'gamernews', 'vuejs'],
   currentSub: 'technews',
   currentOrder: 'new',
   order: ['hot', 'new', 'rising', 'controversial', 'top', 'gilded'],
   search: ''
  },
  actions,
  mutations,
  getters
 })
}

export default store

We can then define our getters (getters.js), allowing us to turn the data reactive within our components, e.g. the posts array.

export const getPosts = state => state.posts
export const getSubreddits = state => state.subreddits
export const getCurrentSub = state => state.currentSub
export const getCurrentOrder = state => state.currentOrder
export const getOrder = state => state.order

We’ll define then our mutations (mutations.js), which will modify in a synchronous way our store data. We can even add some logic to how return the data to the store, simplest scenario a boolean switch state.mystate = !state.mystate.

export default {
 POSTS: (state, posts) => {
  state.posts = posts
 },
 CURRENT_SUB: (state, currentSub) => {
  state.currentSub = currentSub
 },
 CURRENT_ORDER: (state, currentOrder) => {
  state.currentOrder = currentOrder
 }
}

And finally our actions (actions.js) for the asynchronous part, which allow us to commit and dispatch modifications to the store through the mutations.

export const changeSub = ({ commit, state }, sub) => {
 commit('CURRENT_SUB', sub)
}

export const changeOrder = ({ commit, state }, order) => {
 commit('CURRENT_ORDER', order)
}

With these few settings, we’ll be able to submit our information to our source of truth, without reiterate the logic.

Creating the template

We’ll start creating a very basic template in our component/home.vue file.

<template>

 <main>
  <h1>{{getCurrentSub}}</h1>
  <ul>
   <li v-for="subreddit in getSubreddits">
    <a v-on:click="changeSub(subreddit)">{{ subreddit }}</a>
   </li>
  </ul>

  <h2>{{getCurrentOrder}}</h2>
  <ul>
   <li v-for="order in getOrder">
    <a v-on:click="changeOrder(order)">{{ order }}</a>
   </li>
  </ul>

  <article v-for="post in getPosts">
   <h3>{{ post.data.title }}</h3>
  </article>
 </main>

</template>

<script>
import { mapGetters, mapActions } from 'vuex'

export default {
 computed: {
  ...mapGetters([
   'getSubreddits',
   'getCurrentSub',
   'getOrder',
   'getCurrentOrder',
   'getPosts'
  ])
 },

 methods: {
  ...mapActions([
   'changeSub',
   'changeOrder'
  ])
 }
}
</script>

With this very basic template for our news aggregator, without redefine almost absolutely anything, we are already able to modify our store and prepared the loop for our posts, retrieving only the title.

Pressing the link we’ll fire the action changeSub or  changeOrder which will commit the new Subreddit or order.

Data query with Axios and promises

Let’ define our function to retrieve the data from Reddit, add Axios to the package and install using npm install axios --D, then fill our index.js inside the api.

import axios from 'axios'

export default {
 baseUrl: `//www.reddit.com/r/`,

fetchData (current, order) {
 return new Promise((resolve, reject) => {
  axios.defaults.baseURL = this.baseUrl

  return axios.get(`${current}/${order}.json`).then(
   response => {
    resolve(response.data.data.children)
   })
  })
 }
}

In this way we’ll wrap a function with a Promise, returning the query of the URL using Axios, if it resolves to 200. Please note how we do not mention any part single part of the store, we’ll just stick to pass the data. Also note how the structure of Reddit for our news aggregator will bedata.data.children.

Now we have out api function, we can go back to our action.js and create our action to be triggered at every change of Subreddit or Order, updating the URL with the new values (e.g. //www.reddit.com/r/vuejs/new.json).

import api from './../api'

export const commitPosts = ({ commit, state }) => {
 return new Promise((resolve, reject) => {
  api.fetchData(state.currentSub, state.currentOrder).then(
   response => {
    commit('POSTS', response)
    resolve(response)
   },
   response => {
    reject(response)
   }
  )
 })
}

Last touch will be to add the function within the action with commitPosts({ commit, state }), so it will be triggered at every change request, to refresh the post list. E.g.

export const changeSub = ({ commit, state }, sub) => {
 commit('CURRENT_SUB', sub)
 commitPosts({ commit, state })
}

As you can see we’ll pass the commits and states, while the sub and order will be retrieved from the store automatically.

 

Continuous deployment with Netlify

Easiest part of our tutorial will be to integrate the deployment from Git to Netlify. Assuming you have already an account for both, we’ll create a new site from our dashboard homepage.
Inside the Build & deploy section we’ll choose as command npm run build, while as path dist.

Wait the deployment and that’s it!

Yes it’s seriously just all here, you can now access to your app from the Netlify link, then fetch your posts from Reddit using a predefined array of subreddits and orders.

You can see it on work in this sandbox.

Related content:
Styleguide framework: kss

Leave a Reply