Props in class components, vue3 - vue.js

I am migrating my vue2 code into vue3 and using class components. But I am getting an error in props definition. Error message: Argument of type ... is not assignable to parameter of type 'Vue'. How to fix it?
import { Prop } from 'vue-property-decorator';
import { Options, Vue } from 'vue-class-component';
export default class MyComponent extends Vue {
#Prop({ type: [Number, String], default: 0 })
min!: number | string;
}

Related

How to pass props to custom header component using ag-grid?

I'm using Vue 3 with ag-grid and want to setup a new ColDef like so
const colDef: ColDef = {
field: 'objectKey',
headerComponent: ColumnHeader, // my custom component
headerComponentParams: {
foo: 'bar'
},
}
My ColumnHeader component defines its props for now
<script setup lang="ts">
const props = defineProps<{
foo: string;
}>();
</script>
Running the app gives me the following error
[Vue warn]: Missing required prop: "foo" ...
This is because the whole props are undefined.
For reproduction purposes
Plunker snippet https://plnkr.co/edit/OoOD0I8W5NgYX45u which is based on https://www.ag-grid.com/vue-data-grid/component-header/#example-custom-header-component
You will get the error
Missing required prop: "name" ...
Based on https://github.com/ag-grid/ag-grid-vue-example/issues/14 it should work as expected I think. Does someone know what's wrong or missing?
your document clearly states 1. how to use headerComponent in columnDefs and 2. how parameters are passed down to custom header components.
pass a component name as string, just like mounting an external component with <component />. It receives both component itself and mounted component's name in string. My guess is that AgGridVue also uses <component /> internally.
// main.js
data: function () {
return {
rowData: [
{
foo: 'bar',
},
],
columnDefs: [
{
field: 'foo',
headerComponent: 'CustomHeader',
headerComponentParams: {
name: 'test',
},
},
],
};
},
When a Vue component is instantiated the grid will make the grid APIs, a number of utility methods as well as the cell and row values available to you via this.params - the interface for what is provided is documented below.
modify ColumnHeader to use this.params instead of props.
// customHeaderVue.js
export default {
template: `
<div>
*{{ name }}*
</div>
`,
computed: {
name() {
return this.params.name;
},
},
};
working demo: https://plnkr.co/edit/L7X6q3Mr7K0pewO8
edit: ag-grid's IHeaderParams does not support generics. to extend given type without generics, please use these methods.
import type { IHeaderParams } from "ag-grid-community";
// fig 1
// combine with type intersection
type CustomHeaderParams = IHeaderParams & { name: string };
// fig2
// combine with interface extension
interface CustomHeaderParams extends IHeaderParams {
name: string;
}
here are typed examples of CustomHeader.vue
// fig 1. Vue3 composition API, with <script setup>
<script lang="ts" setup>
import { defineProps, onMounted, ref } from "vue";
import type { IHeaderParams } from "ag-grid-community";
type CustomHeaderParams = IHeaderParams & { name: string };
const props = defineProps<{ params: CustomHeaderParams }>();
const name = ref(props.params.name);
</script>
<template>
<div>*{{ name }}*</div>
</template>
// ------
// 2. Vue3 options API, without <script setup>
<script lang="ts">
import { defineComponent, type PropType } from "vue";
import type { IHeaderParams } from "ag-grid-community";
type CustomHeaderParams = IHeaderParams & { name: string };
export default defineComponent({
props: {
params: {
type: Object as PropType<CustomHeaderParams>,
},
},
setup(props) {
return { name: props.params?.name ?? "" };
},
});
</script>
<template>
<div>*{{ name }}*</div>
</template>
note: I've suggested using the component's name in columnDefs.headerComponent because the official document says so, and seems fine in the working demo; but it actually depends on the Vue API. I assume it has something to do with Vue internal variable scope.
Vue Options API(Class based component): put component name string.
Vue Compositions API(Functional component): put the component variable itself.

Vue 2: How to unit test component that uses Chart.js (vue-chart-3)

I have a vue2 project that uses ClassComponents and Chart.js (via vue-chart-3). I now have a simple component that wraps a DoughnutChart to manage data and stuff.
DBOverviewDoughnut.vue
<template>
<div>
<p>test</p>
<DoughnutChart ref="doughnutRef" :chartData="sharesData"></DoughnutChart>
</div>
</template>
<script lang="ts">
import Component from 'vue-class-component';
import Vue from 'vue';
import { DoughnutChart, ExtractComponentData } from 'vue-chart-3';
import { Prop, Ref } from 'vue-property-decorator';
import { ChartData } from 'chart.js';
#Component({ components: { DoughnutChart } })
export default class DBOverviewDoughnut extends Vue {
#Prop()
private sharesData!: ChartData<'doughnut'>;
#Ref()
private readonly doughnutRef!: ExtractComponentData<typeof DoughnutChart>;
created(): void {
this.assignColors();
}
mounted(): void {
if (this.doughnutRef.chartInstance) {
console.log(this.doughnutRef.chartInstance.data);
}
}
assignColors(): void {
this.sharesData.datasets[0].backgroundColor = [
'#77CEFF',
'#0079AF',
'#123E6B',
'#97B0C4',
'#A5C8ED',
];
}
}
</script>
Starting the program it will work fine and I can access the chartInstance inside the mounted hook.
But now I want to unit test my component. I thought on setting the propsData which will be the input data for the chart.
DBOverviewDoughnut.spec.ts
import DBOverviewDoughnut from '#/components/dashboard/DBOverviewDoughnut.vue';
import { mount, Wrapper } from '#vue/test-utils';
import { Share } from '#/Share';
describe('DBOverviewDoughnut', () => {
let cut: Wrapper<DBOverviewDoughnut>;
it('should render the correct amount of sections', () => {
cut = mount(DBOverviewDoughnut, {
propsData: {
sharesData: {
labels: ['TestShare1', 'TestShare2', 'TestShare3'],
datasets: [{ data: [11, 22, 33] }]
}
}
});
const chart = cut.findComponent({ ref: 'doughnutRef' });
console.log(chart);
});
});
Using shallowMount() doesn't seem to work, because I only get this from logging (no chartInstance and its properties as in the production code):
VueWrapper {
isFunctionalComponent: undefined,
_emitted: [Object: null prototype] {},
_emittedByOrder: [],
selector: { ref: 'doughnutRef' }
}
So I thought maybe I have to mount the component because the DoughnutChart is also a wrapper around the Chart.js charts. But when using mount() I get the following error:
console.error node_modules/vue/dist/vue.runtime.common.dev.js:621
[Vue warn]: `createElement()` has been called outside of render function.
console.error node_modules/vue/dist/vue.runtime.common.dev.js:621
[Vue warn]: Error in render: "Error: [vue-composition-api] must call Vue.use(VueCompositionAPI) before using any function."
found in
---> <DoughnutChart>
<DBOverviewDoughnut>
<Root>
I don't really know what I'm doing wrong. I registered the VueCompostionAPI in my main.ts:
import Vue from 'vue';
import { Chart, registerables } from 'chart.js';
import App from './App.vue';
import router from './router';
import store from './store';
import VueCompositionAPI from '#vue/composition-api';
Chart.register(...registerables);
Vue.use(VueCompositionAPI);
new Vue({
router,
store,
render: (h) => h(App),
}).$mount('#app');
Following this post doesn't solve the problem either.
Anyone got an idea what's going wrong? I'm a bit confused if the error has to do with my test setup or with the installation of chart.js or compositionApi.
You need to use VueCompositionAPI inside your spec as well when you mount the component. You can do this by creating a local Vue instance inside your spec, adding VueCompositionAPI as a plugin to the instance and using the instance when you mount the component. https://vue-test-utils.vuejs.org/api/options.html#localvue
Using localVue is really what I should have thought about. This and installing the canvas-package works, that I get additional information about my Ref-Element. However I still have to figure out what to do with it.
#AdriHM I want to test if the rendered chat gets the correct data I guess. Or if it displays it correctly (e.g. display the correct amount of sections) But the longer I think about it the less I'm sure it's the right thing to test. I don't want to test the Chart.js API though.

vue3 class component access props

i use vue3 with class-component in typescript my class looks like:
import {Options, Vue} from "vue-class-component";
#Options({
props: {
result: Object
}
})
export default class imageResult extends Vue {
currentImage = 0;
getSlides(){
console.log('result',this.$props.result); // not working
console.log('result',this.result); // not working too
}
My question is, how can i access and use the property within my class?
both this.result and this.$props.result throws me an error.
can someone help me?
Thanks in advance
late answer but maybe it helps someone in the future.
works for me with vu3 & typescript
<script lang="ts">
import { Vue, prop } from "vue-class-component";
export class Props {
result = prop<string>({ required: true });
}
export default class Foo extends Vue.with(Props) {
test(): void {
console.log(this.result);
}
}
</script>
My suggestion to you is to follow the documentation on using Typescript with vue using class component: enter link description here
in order to fix your code I think this should work:
import {Vue} from "vue-class-component";
import {Component} from "vue-class-component";
// Define the props by using Vue's canonical way.
const ImageProps = Vue.extend({
props: {
result: Object
}
})
// Use defined props by extending GreetingProps.
#Component
export default class ImageResult extends ImageProps {
get result(): string {
console.log(this.result);
// this.result will be typed
return this.result;
}
}

Uncaught (in promise) TypeError: Cannot read property '$store' of undefined

I want to build an application with vue3 and vuex. I have got an error when I use $store (Uncaught (in promise) TypeError: Cannot read property '$store' of undefined). But I did not find anything about that. Could you help me?
import { createApp } from "vue";
import App from "./App.vue";
import router from "./router";
import { store } from "./store";
createApp(App)
.use(store)
.use(router)
.mount("#app");
main.js
<template>
{{ posts }}
<button #click="clickOnButton">Tıkla</button>
</template>
<script>
export default {
name: "HelloWorld",
props: {
msg: String
},
computed: {
posts: () => this.$store.state.post
},
methods: {
clickOnButton: () => {
this.$store.commit("getPost");
}
}
};
</script>
component
The problem has nothing to do with your store. You are using an arrow function when you should be using the standard method syntax:
// ...
methods: {
clickOnButton() {
// this will now point to your component.
this.$store.commit('getPost');
}
}
// ...
The foremost limitation of arrow functions is that they are totally agnostic of context: this is not bound. See the vue documentation
and the arrow functions documentation:
An arrow function expression is a compact alternative to a traditional function expression, but is limited and can't be used in all situations.
Differences & Limitations:
Does not have its own bindings to this or super, and should not be used as methods.

Vue: lazy loading from library (Bootstrap-Vue) inside a component

I am exploring the lazy-loading feature and I'm trying to use it for Bootstrap-Vue component, but it does not work.
If I import the b-card "normally", it gets renderred correctly:
import { BCard } from 'bootstrap-vue';
export default {
components: {
BCard
}
};
But when I'm attempting the 'lazy-load' syntax it does not work:
export default {
components: {
BCard: () => import('bootstrap-vue').BCard
}
};
The b-card component is not renderred, but no error is thrown and in Chrome's DOM inspection tools I can see that the placeholder <!----> is placed by Vue where the b-card component should be. I suspect that the library object that is loaded does not have the BCard property, but I don't know how else to access the library component with the 'lazy' syntax.
Is it possible to lazy-load a module from a library? How to do it?
When dynamically importing a module, you use the import keyword as a function and it returns a promise. So, to access the module component, you can use this syntax:
export default {
components: {
BCard: () => import('bootstrap-vue').then(module => module.BCard)
}
}