Sub-class a vue component - vue.js

I have some repeating functionality that many components will use. So my first thought is I should create a base component and have my components inherit/subclass from this.
But maybe this is not possible in vue.js and maybe its not the vue.js way to do things? If I explain my use-case can you suggest how best I implement this in vue.js?
BasePage.vue
<template>
...
</template>
<script>
export default {
name: 'animated-page',
watch: {
'$route' (to, from) {
// conditionally choose animations depending on route
const toDepth = to.path.split('/').length
const fromDepth = from.path.split('/').length
this.transitionName = toDepth < fromDepth ? 'slide-right' : 'slide-left'
}
}
}
</script>
<style>
... my animations
</style>
About.vue
<template>
...
</template>
<script>
export default {
// somehow inherit from BasePage
name: 'about-page',
... 'about' specific code
}
</script>
<style>
</style>

You can use extend to subclass the Vue constructor, and you can use it on components to subclass them.
In general, though, you should prefer composition to inheritance and use mixins where you can.
Mixins are a flexible way to distribute reusable functionalities for Vue components. A mixin object can contain any component options. When a component uses a mixin, all options in the mixin will be “mixed” into the component’s own options.

Related

Call a function from another component using composition API

Below is a code for a header and a body (different components). How do you call the continue function of the component 2 and pass a parameter when you are inside component 1, using composition API way...
Component 2:
export default {
setup() {
const continue = (parameter1) => {
// do stuff here
}
return {
continue
}
}
}
One way to solve this is to use events for parent-to-child communication, combined with template refs, from which the child method can be directly called.
In ComponentB.vue, emit an event (e.g., named continue-event) that the parent can listen to. We use a button-click to trigger the event for this example:
<!-- ComponentB.vue -->
<script>
export default {
emits: ['continue-event'],
}
</script>
<template>
<h2>Component B</h2>
<button #click="$emit('continue-event', 'hi')">Trigger continue event</button>
</template>
In the parent, use a template ref on ComponentA.vue to get a reference to it in JavaScript, and create a function (e.g., named myCompContinue) to call the child component's continueFn directly.
<!-- Parent.vue -->
<script>
import { ref } from 'vue'
export default {
⋮
setup() {
const myComp = ref()
const myCompContinue = () => myComp.value.continueFn('hello from B')
return {
myComp,
myCompContinue,
}
},
}
</script>
<template>
<ComponentA ref="myComp" />
⋮
</template>
To link the two components in the parent, use the v-on directive (or # shorthand) to set myCompContinue as the event handler for ComponentB.vue's continue-event, emitted in step 1:
<template>
⋮
<ComponentB #continue-event="myCompContinue" />
</template>
demo
Note: Components written with the Options API (as you are using in the question) by default have their methods and props exposed via template refs, but this is not true for components written with <script setup>. In that case, defineExpose would be needed to expose the desired methods.
It seems like composition API makes everything a lot harder to do with basically no or little benefit. I've recently been porting my app to composition API and it required complete re-architecture, loads of new code and complexity. I really don't get it, seems just like a massive waste of time. Does anyone really think this direction is good ?
Here is how I solved it with script setup syntax:
Parent:
<script setup>
import { ref } from 'vue';
const childComponent = ref(null);
const onSave = () => {
childComponent.value.saveThing();
};
</script>
<template>
<div>
<ChildComponent ref="childComponent" />
<SomeOtherComponent
#save-thing="onSave"
/>
</div>
</template>
ChildComponent:
<script setup>
const saveThing = () => {
// do stuff
};
defineExpose({
saveThing,
});
</script>
It doesn't work without defineExpose. Besides that, the only trick is to create a ref on the component in which you are trying to call a function.
In the above code, it doesn't work to do #save-thing="childComponent.saveThing", and it appears the reason is that the ref is null when the component initially mounts.

VueJS extend component for customization

Is there any way to use some Generic component and keep it's props emitters etc and just customize it?
Example:
<template>
<GenericComponent color="black">
Something in the default slot
</GenericComponent>
</template>
<script>
import GenericComponent from 'GenericComponent'
export default {
name: 'MyCustomizedComponent'
props: // to take same props as GenericComponent and pass it to GenericCompnent?
// and it emits all events from GenericComponent
// I could probably just copy props and pass it directly to GenericComponent, but what if there
// is many
}
</script>
<style scoped>
//some changes to Generic component
<style>
So I could just create props, and define all # from GenericComponent and emit them same way, but is there any easy way to do it ?
You can either use mixins, to reuse code across several components.
You can also create a wrapper component to the original component, do your customizations, and use v-bind="$props" to propagate all props to the original component and v-on="$listeners" to emit all events from the original component to the parent.
I'm not sure what is best for your case.

Sub-components in Vue functional components

As I was playing around with functional components in Vue, I realized that the components property isn't supported to declare sub-components for functional components. Trying do so will result in an Unknown custom element exception.
Except for using global components, is there any way to use sub-components in functional components?
It is a bit of a workaround, but this Github comment suggest using inject instead to inject the component.
<template functional>
<div>
<component :is="injections.components.SomeChildren"></component>
</div>
</template>
<script>
import SomeChildren from "./SomeChildren.vue";
export default {
inject: {
components: {
default: {
SomeChildren
}
}
}
};
</script>
Not as simple as for regular components, but it does the job.

Vue.js override mounted() method

So I've extended my component this way:
<template>
<qlist></qlist>
</template>
<script>
import QuestionList from '#/components/pages/questions/QuestionList'
export default {
extends: QuestionList,
components: {
'qlist': QuestionList
}
}
</script>
<style scoped>
</style>
How can I totally override mounted() method from my QuestionList component? I've noticed that when I define it in current component, I can add functionality to the mounted() method, but I'm not able to override the previous behaviour
Your component imports QuestionList to include it as an ELEMENT in the template, so whatever you have in the mounted() hook of QuestionList will be called, you cannot avoid it doing it this way. Use a mixin instead: put all the logic in there that should be the same in both the original component and the component that extends it and separately implement mounted() in each. By moving your mounted() hook logic to a method you could even use the custom merge options as described here: https://v2.vuejs.org/v2/guide/mixins.html but it's probably not necessary.

pass router params to component object

I have view and I want to load svg based on router params, I have installed vue-loader and it is working if I hard code it.
<template>
<div>
<suit/>
</div>
</template>
<script>
export default {
components:{
suit: ()=>import('../assets/svg/'+Param+'.svg')
}
}
</script>
<style>
</style>
This is what I have. Instead of Param I want to get this.route.params, but when I try this I get undefined which is logical because components wrapper is object. Is there a way to pass a variable here or must I redo the whole thing?
Instead of this.route.params, within a component you should be using this.$route.params. Vue Router Docs.