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.