I have an admin panel route with 2 nested child routes that render 2 views – AdminBrands
renders brands and allow the admin to see and delete brands, and AdminCategories
where the admin can see and delete categories. Now the problem is that those 2 views are super similar to each other, but at the same time they have got some differences, so I’m not exactly sure how to DRY them up or even if I should.
The template is mostly the same, with just a couple of small differences. The script part is literally identical, except that the data and functions have different names – getCategories()
and getBrands()
for example. Should I try creating some all-mighty view that changes depending on the current route, or stick to having more than 1 components? I’ve given 2 views as my example, but in my project, I actually have 5 views that are extremely similar, so creating a single view would allow me to remove 4 of them which will make the folder structure a lot cleaner.
{
path: "/admin",
component: AdminPanel,
children: [
{
path: "categories",
name: "adminCategories",
component: AdminCategories
},
{
path: "brands",
name: "adminBrands",
component: AdminBrands
}
]
}
AdminBrands view:
<template lang="html">
<div class="admin-brands">
<div class="column-title">
<p>Марки</p>
<div class="create-button" @click="goToCreateBrand()">Добави</div>
</div>
<div class="brands">
<div class="brand" v-for="brand in brands">
<p>{{ brand.name }}</p>
<div class="btn btn-danger" @click="deleteBrand(brand.id)">Delete</div>
</div>
</div>
<Pagination :currentPage="currentPage" :totalPages="totalPages" @setCurrentPage="setCurrentPage"></Pagination>
</div>
</template>
<script>
import axios from 'axios'
import router from "../../router"
import Pagination from "../Pagination"
export default {
components: {
Pagination
},
data() {
return {
brands: null,
currentPage: 1,
totalItems: null,
totalPages: null
}
},
methods: {
async getBrands(){
try {
let response = await axios.get("/brands?page=" + this.currentPage)
this.brands = response.data.brands
this.totalItems = response.data.totalItems
this.totalPages = response.data.totalPages
} catch (e) {
console.log(e)
}
},
async deleteBrand(brandId){
let response = await axios.post('/deleteBrand', { brandId })
console.log(response)
},
setCurrentPage(page){
this.currentPage = page
},
goToCreateBrand(){
router.push({ name: "createBrand" })
}
},
mounted(){
this.getBrands()
}
}
</script>
<style lang="css" scoped>
.column-title {
margin-bottom: 0px;
}
.brand {
display: flex;
align-items: center;
border-bottom: 1px solid #dddddd;
padding: 20px 0;
font-size: 15px;
}
.btn {
margin-left: auto;
width: 100px;
}
</style>
AdminCategories view:
<template lang="html">
<div class="admin-categories">
<div class="column-title">
<p>Категории</p>
<div class="create-button" @click="goToCreateCategory()">Добави</div>
</div>
<div class="categories">
<div class="category" v-for="category in categories">
<div class="category-image-container">
<img class='category-image' :src="category.image" :alt="category.name">
</div>
<p>{{ category.name }}</p>
<div class="btn btn-danger" @click="deleteCategory(category.id)">Delete</div>
</div>
</div>
<Pagination :currentPage="currentPage" :totalPages="totalPages" @setCurrentPage="setCurrentPage"></Pagination>
</div>
</template>
<script>
import axios from 'axios'
import router from "../../router"
import Pagination from "../Pagination"
export default {
components: {
Pagination
},
data() {
return {
categories: null,
currentPage: 1,
totalItems: null,
totalPages: null
}
},
methods: {
async getCategories(){
let response = await axios.get("/categories?page=" + this.currentPage)
this.categories = response.data.categories
this.totalItems = response.data.totalItems
this.totalPages = response.data.totalPages
},
async deleteCategory(categoryId){
let response = await axios.post("/deleteCategory", { categoryId })
console.log(response)
},
setCurrentPage(page){
this.currentPage = page
},
goToCreateCategory(){
router.push({ name: "createCategory" })
}
},
watch: {
currentPage(){
this.getCategories()
}
},
mounted(){
this.getCategories()
}
}
</script>
<style lang="css" scoped>
.column-title {
margin-bottom: 0px;
}
.category {
display: flex;
align-items: center;
border-bottom: 1px solid #dddddd;
padding: 20px 0;
font-size: 15px;
}
.category-image-container {
display: flex;
justify-content: center;
align-items: center;
width: 100px;
height: 100px;
margin-right: 15px;
}
.category-image {
display: block;
max-height: 100%;
max-width: 100%;
}
.btn {
margin-left: auto;
width: 100px;
}
</style>
Advertisement
Answer
For now, the best solution is to abstract completely identical (common) functions with Vue Mixins such as setCurrentPage
and most of your data points currentPage
, totalItems
and totalPages
. Furthermore, you can abstract very similar functions, such as getCategories
/getBrands
and deleteCategory
/deleteBrand
. Here it’s best if you abstract each pair into one function, taking an input whether it’s about categories or brands, and then working with them.