Logo credits to vuejs.org and golang.org

Social application with Vue.js and GO

Create and serve a twitter like application with vue.js and golang PART 3: COMPONENTS & SLOTS

Ivano Dalmasso
Feb 22 · 8 min read

This is the third part of this serie. Check here all the parts:

In this lesson we are going to add some frontend functionalities, and by doing this we’ll learn a little more about using components and how to extend them with slots.

Main target here is to update the frontend project, you can find the code here.

Reusing components for posts

In the last lesson we created a list where we could see the posts of a user. Now, that view was not really nice, so here we are going to add css to make it a little better.

Additionally, we are going to create a card component that we’ll use again in other places in our application, so that the feeling that a user will have overall will be always the same.

Create a new component in the UI folder, and call it “BaseCard.vue”. Inside write the following code:

<template>
<div>
<header v-if="$slots.header">
<slot name="header"> </slot>
</header>
<slot></slot>
<footer v-if="$slots.footer && expandable">
<a href="#" @click.prevent="toggleDetails" class="footer-elements"> {{ messageValue }}</a>
<slot name="footer" v-if="expanded"></slot>
</footer>
</div>
</template>
<script>
export default {
props: {
expandable: {
type: Boolean,
default: false
}
},
data() {
return {
expanded: false
};
},
computed: {
messageValue() {
if (!this.expanded) return "Show";
return "Hide";
}
},
methods: {
toggleDetails() {
this.expanded = !this.expanded;
}
}
};
</script>
<style scoped>
.right-icon {
float: right;
margin: 2rem auto;
border-radius: 12px;
}
.footer-elements {
display: flex;
float: left;
}
header {
display: flex;
justify-content: space-between;
align-items: center;
color: brown;
}
div {
margin: 2rem auto;
max-width: 50rem;
border-radius: 12px;
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.26);
padding: 1rem;
}
footer {
padding: 1rem;
}
</style>

The style tag is actually filled with the minimum code that lets the application have a better look, but of course this is for demonstrative purpouses.

Let’s explain briefly the scripts tag: there is one prop, actually, called “expandable” that will be passed from outside to this component. Note that this time the declaration of the prop is made in a different way: in fact the props parameter accept both an array or a javascript object, and in this second case we can pass the props as objects where we can describe better the kind of properties the prop should have.

In this example, we have set a type, Boolean, and a default value of false for the “expandable” prop.

The data property contains only a value that indicates if the card actually is or is not expanded, and we have a method “toggleDetails” that toggles the expansion of the card.

There is also another configuration option that we have never seen before: “computed”.

This is an object containing functions that can be actually used as data values. So we can use them (also in the template) as if they were data values (no parentheses needed), and they also have reactive behaviour.

In fact, vue internally knows if some dependency of a computed value is changed, and re-evaluate the computed method only when needed. In our code, the messageValue computed properties will automatically be re-evaluated only when “this.expanded” changes.

Note that in the methods and computed properties we can use any other data, method or computed property using the keyword “this”, as if we were in an object of a class.

Lastly, the template: here there are two new things added, v-if and the slots.

The v-if construct creates or deletes an element from the DOM depending on the validity of a condition. This have to be written exactly as with the v-for, so we can also insert a little logic here (but normally is always better create a computed property or a method to do this, so we can have more separation from code).

In our example, the header section is rendered only if there is a $slots.header variable set.

Last and more important point, is the use of slots. Actually, using a slot in a component let the developer to insert from the outside some content and then extends the internal component.

It works like this: everything that is inserted in the father template into the value of the component tag of the child, is actually inserted where the “slot” tag is in the child component. This is all you need to do in case of only one slot.

If you need to set more then one slot, like in the example above, you can insert “named slots” in the children, so slots have a name parameter.

In this last scenario, one slot can also be left without name, and this will be actually called “default slot”. In the father, in this case, the value to be replaced should be placed inside an additional tag “template” and a parameter “v-slot:slotName” where slotName is the name of the slot of the child.

Again in the example we need to insert in the father a “<template v-slot:header>” tag for inserting into the header slot.

Now in the file SinglePost.vue we can change the template to use this component BaseCard in the actual SinglePost template:

<template>
<base-card :expandable="post.comments && post.comments.length > 0">
<template v-slot:header>
<h3>{{ postTitle(post) }}</h3>
</template>
{{ post.post }}
<template v-slot:footer>
<base-card v-for="comment in post.comments" :key="comment.id" :expandable="false">
<template v-slot:header>
<h3>{{ postTitle(comment) }}</h3>
</template>
{{ comment.post }}
</base-card>
</template>
</base-card></template>

Here we used a card that can be expanded if there are some comments, so actually the post object is going to have an upgrade to also contain them.

In the header we can see the title of the post, and as a footer we inserted another list of cards that contains each a comment.

For semplicity of code we used the same structure for comment and post objects, so they can share some functions (like “postTitle”).

Next thing to do, go in the Posts.Vue component and change the “posts” variable to contain some comments in every post, i.e.

posts: [        
{
id: 1,
user: "idalmasso",
date: "2021-01-19 15:30:30",
post:"Hello",
comments: [
{
id: 3,
user: "Nostradamus",
date: "2021-01-20 20:30:34",
post: "LOL"
},
{
id: 4,
user: "FinnishMan",
date: "2021-01-20 20:30:34",
post: "Please..."
}
]
}
]

We did not import the BaseCard into our component: because we’ll propably reuse this “Base” component a lot inside of our app, we can import it inside the initial Vue object, in main.js, and then set it in the “components” configuration parameter of the Vue global instance.

Changing the about page

Just to complete a little more the app, also change the “About.vue” component in a User.vue, that will show the user actually logged in and his posts.

<template>
<div class="container">
<h1>{{ user.username }}</h1>
<p>{{ user.description }}</p>
<post-list :posts="posts" title="Last Posts">
</post-list>
</div>
</template>
<script>
import PostList from "../components/PostList.vue";
export default {
components: { PostList },
data() {
return {
user: {
username: "idalmasso",
description: "This is only a little description"
},
posts: [
...
]
};
}
};</script>

This is actually self explainatory: the page is a simple one with a title consisting of the user’s username and shows a little description before displaying all of his posts. Actually the data is still some dummy block of objects, we’ll fix that later.

Setting a “title” parameter in the post-list, requires to also set that value in the props configuration parameters of the child component. Moreover, having removed the “About” component, we have to also update the index.js file inside the router to point to this new component.

At this point, it is mandatory a little explanation regarding the vue router: it is actually a component that lets you organize, maintain and correct the routing between different pages in your application. In a PWA the page is not loaded from the server each time, but this behaviour is actually simulated by the code, and this is what the router actually does.

First thing to do, is to check if in the main.js file the router is actually imported and registered in the vue application.

The router lets the application to have different pages, and show them only when called. Also it interacts with the browser “back” and “forward” buttons, so that this will seem to the user as if multiple pages are called and showed, while in reality only the routed piece of the page is changed by scripts.

As we seen before, the two foundamental tags that interact with the vue-router are <router-link> and <router-view>: the first is an actual link (it is actually a component that extends the <a> tag) and requires a “to” parameter, that can also be a javascript object, that points to the page to be shown. The other one is a placeholder that says to the router where is the point where the routed view should be injected.

Looking at the index.js file in the router folder we can see also the configuration of the router:

Vue.use(VueRouter);

This first line instruct Vue to use the VueRouter. Then at the end of the file:

const router = new VueRouter(
{
mode: "history",
base: process.env.BASE_URL,
routes}
);
export default router;

Essentially it creates a VueRouter and export it, using as base url the base url of the application, using mode “history”, so it will use the history pushstate API to achieve url navigation (more HERE ) and then pass at the constructor the list of the routes that it will have to use.

This list of routes is defined in the lines before this construct:

const routes = [
{
path: "/",
name: "Posts",
component: Posts
},
{
path: "/user",
name: "User",
component: () => import("../views/User.vue"),
props: true
}
];

This is a list of objects that defines the actual routes that can be called from the router. In each object is set the path of the url (so, in the example, the root will be served by the first route, while the “/user” path by the second), a name, and the component that will be used to render the router view on that route.

The name is normally used in the router links to reference the route of the link decoupling it from the url (we have done it before, in the application bar, the router-links tag have a to parameter that points to an object with a name parameter set, remember?)

At last, we have the props configuration parameter, set to true in the last route: with that we instruct the vue-router that the router-links can pass some props to the component. This is important, because the default behaviour of the router is to not pass these props.

Having set this to true, in future we will be able to change the calls to the User page to have a different user.

With this we have done a little regression on vue router, if you now launch the frontend the application starts looking better, the data is still static and cannot be changed by the user, but we’ll be there in the next couple of lessons.

Nerd For Tech

From Confusion to Clarification

Medium is an open platform where 170 million readers come to find insightful and dynamic thinking. Here, expert and undiscovered voices alike dive into the heart of any topic and bring new ideas to the surface. Learn more

Follow the writers, publications, and topics that matter to you, and you’ll see them on your homepage and in your inbox. Explore

If you have a story to tell, knowledge to share, or a perspective to offer — welcome home. It’s easy and free to post your thinking on any topic. Write on Medium

Get the Medium app

A button that says 'Download on the App Store', and if clicked it will lead you to the iOS App store
A button that says 'Get it on, Google Play', and if clicked it will lead you to the Google Play store