{"id":271548,"date":"2018-06-19T06:56:28","date_gmt":"2018-06-19T13:56:28","guid":{"rendered":"http:\/\/css-tricks.com\/?p=271548"},"modified":"2018-06-22T11:45:37","modified_gmt":"2018-06-22T18:45:37","slug":"building-a-rss-viewer-with-vue-part-2","status":"publish","type":"post","link":"https:\/\/css-tricks.com\/building-a-rss-viewer-with-vue-part-2\/","title":{"rendered":"Building a RSS Viewer With Vue: Part 2"},"content":{"rendered":"
Welcome to Part 2 of this mini-series on building a RSS viewer with Vue. In the last post<\/a>, I walked through how I built my demo using Vue.js and Vuetify<\/a> on the front end and Webtask<\/a> on the back end. When I built that initial version, I knew it was exactly thatmdash;an “initial” version. I took some time to work on a few updates, and while I won’t dare call this a “perfect” version, I do think I’ve made some improvements and I’d like to share them with you.<\/p>\n <\/p>\n Before I get started, here are links to the completed demo and source code.<\/p>\n Feel free to fork, file PRs, and report bugs to your heart’s content!<\/p>\n When I shared the initial version in Part 1, I outlined some ideas to improve the RSS reader, including:<\/p>\n That was the plan, and like most plans, I wasn’t necessarily able to hit everything in this update (and I’ll explain why at the end). But hopefully you’ll see the improvements as a general “moving in the right direction” for the application. With that out of the way, let’s get started!<\/p>\n I’ll start off discussing the biggest change to the application, the addition of Vuex<\/a>. As I said in the previous post, Vuex describes itself as a “state management pattern + library” on their “What is Vuex”<\/a> page. No offense to their documentation, but I had a difficult time wrapping my head around exactly what this meant, from a practical sense.<\/p>\n After having using it in a few small projects now, I’m coming to appreciate what it provides. To me, the core benefit is providing a central interface to your data. If I’ve got a basic Vue app working with an array of values, I may have multiple different methods that modify it. What happens when I begin to have certain rules that must be applied before the data changes? As a simple example, imagine an array of RSS feeds. Before I add a new one, I want to ensure it doesn’t already exist in the list. If I have one method that adds to the feed list, that isn’t a problem, but if I have more, it may become cumbersome to keep that logic in sync across the different methods. I could simply build a utility to do this, but what happens when I have other components in play as well?<\/p>\n While it is absolutely not<\/strong> a one-to-one comparison, I feel like Vuex reminds me of how Providers or Services work in Angular. If I ever want to do work with any data, I’ll ensure I use a central provider to handle all access to that data. That’s how I look at Vuex.<\/p>\n So the big change in this application was to migrate all the data related items to a store. I began by adding the library to my HTML:<\/p>\n Woot! Half-way done! (OK maybe not.)<\/p>\n I then created an instance of my store in my JavaScript file:<\/p>\n and included it in my Vue app:<\/p>\n Now comes the interesting part. Any time my Vue application needs data, which primarily consists of the list of feeds and the items from those feeds, it’s going to ask the store for them. So, for example, my This is now defined in the Notice that Back in the store, the logic is pretty much the same:<\/p>\n I say “pretty much the same” except now I’m doing a bit of error-checking on the value read in from What you’ll notice is that pretty much all of the actual logic is now gone and all I’m really doing here is UI<\/abbr> stuff. Open a modal here, add an error there, and so forth.<\/p>\n You can view the complete store here<\/a>, although I apologize for lumping everything together in one file.<\/p>\n One of the other changes I mentioned was beginning to “component-ize” the view layer. I ended up only making one component, It isn’t a huge<\/em> change by any means, but it did make it bit easier for me when I started working on the feed display. As I’m not using a fancy builder yet, I defined my component straight in JavaScript like so:<\/p>\n I’m not doing anything at all fancy in heremdash;there’s no dynamic logic or events or anything like that, but I could certainly add that later where it makes sense. I did finally get around to adding the date and time of posting. If you’re curious about how I built the formatter used for it, read my article Build A i18n Filter Using Vue.js & Native Web Specs.”<\/a><\/p>\n Oh, and I finally added a way to delete feeds:<\/p>\nArticle Series:<\/h4>\n
\n
The Plan<\/h3>\n
\n
Implementing Vuex<\/h3>\n
<script src=\"https:\/\/unpkg.com\/vuex\"><\/script><\/code><\/pre>\n
const feedStore = new Vuex.Store({\r\n \/\/ lots of stuff here\r\n});<\/code><\/pre>\n
let app = new Vue({ \r\n el: '#app',\r\n store:feedStore,\r\n \/\/ lots of stuff here too...\r\n});<\/code><\/pre>\n
feeds<\/code> value is now computed:<\/p>\n
feeds() {\r\n return feedStore.state.feeds;\r\n},<\/code><\/pre>\n
state<\/code> portion of my store:<\/p>\n
state: {\r\n allItems: [],\r\n feeds: [],\r\n selectedFeed: null\r\n},<\/code><\/pre>\n
feeds<\/code> defaults to an empty array. I had previously used the
created<\/code> event of my Vue app to read in the data from
localStorage<\/code>. Now, I ask the store to do that:<\/p>\n
created() {\r\n feedStore.dispatch('restoreFeeds');\r\n},<\/code><\/pre>\n
restoreFeeds(context) {\r\n let feedsRaw = window.localStorage.getItem('feeds');\r\n if(feedsRaw) {\r\n try {\r\n let feeds = JSON.parse(feedsRaw);\r\n context.state.feeds = feeds;\r\n context.state.feeds.forEach(f => {\r\n context.dispatch('loadFeed', f);\r\n });\r\n } catch(e) {\r\n console.error('Error restoring feed json'+e);\r\n \/\/ bad json or other issue, nuke it\r\n window.localStorage.removeItem('feeds');\r\n }\r\n }\r\n},<\/code><\/pre>\n
localStorage<\/code>. But here’s the crucial bit. I already said I failed in terms of switching to IndexedDB, but in theory, I could build a third version of this application with an updated store and my Vue app won’t know the difference. And that’s where I started to get really excited. The more I worked, the more “dumb” my Vue app became and the less tied it was to any particular implementation of storage. Let’s look at the complete Vue app now:<\/p>\n
let app = new Vue({ \r\n el: '#app',\r\n store:feedStore,\r\n data() {\r\n return {\r\n drawer:true,\r\n addFeedDialog:false,\r\n addURL:'',\r\n urlError:false,\r\n urlRules:[],\r\n selectedFeed:null\r\n }\r\n },\r\n computed: {\r\n showIntro() {\r\n return this.feeds.length == 0;\r\n },\r\n feeds() {\r\n return feedStore.state.feeds;\r\n },\r\n items() {\r\n return feedStore.getters.items;\r\n }\r\n },\r\n created() {\r\n feedStore.dispatch('restoreFeeds');\r\n },\r\n methods:{\r\n addFeed() {\r\n this.addFeedDialog = true;\r\n },\r\n allFeeds() {\r\n feedStore.dispatch('filterFeed', null);\r\n },\r\n addFeedAction() {\r\n this.urlError = false;\r\n this.urlRules = [];\r\n\r\n feedStore.dispatch('addFeed', {url:this.addURL})\r\n .then(res => {\r\n this.addURL = '';\r\n this.addFeedDialog = false;\r\n })\r\n .catch(e =>{\r\n console.log('err to add', e);\r\n this.urlError = true;\r\n this.urlRules = [\"URL already exists.\"]; \r\n });\r\n },\r\n deleteFeed(feed) {\r\n feedStore.dispatch('deleteFeed', feed);\r\n },\r\n filterFeed(feed) {\r\n feedStore.dispatch('filterFeed', feed);\r\n }\r\n }\r\n})<\/code><\/pre>\n
Adding a Component<\/h3>\n
feed-item<\/code>. This reduced the total number of lines in the HTML a bit:<\/p>\n
<v-flex xs12 v-for=\"item in items\" :key=\"item.link\">\r\n <feed-item :title=\"item.title\" :content=\"item.content\" :link=\"item.link\" :feedtitle=\"item.feedTitle\" :color=\"item.feedColor\" :posted=\"item.pubDate\"><\/feed-item>\r\n<\/v-flex><\/code><\/pre>\n
Vue.component('feed-item', {\r\n props:[\r\n 'color','title','content','link','feedtitle', 'posted'\r\n ],\r\n template: `\r\n <v-card :color=\"color\">\r\n <v-card-title primary-title>\r\n <div class=\"headline\">{{title}} ({{posted | dtFormat}})<\/div>\r\n <\/v-card-title>\r\n <v-card-text>\r\n {{content | maxText }}\r\n <\/v-card-text>\r\n <v-card-actions>\r\n <v-btn flat target=\"_new\" :href=\"link\">Read on {{feedtitle}}<\/v-btn>\r\n <\/v-card-actions>\r\n <\/v-card> \r\n `\r\n});<\/code><\/pre>\n
The Power of Delete!<\/h3>\n