Vue: How do I call a function inside a Component? - vue.js

I am trying to execute a function from the Food.vue component from Page.vue.
How can I execute a function from an imported component?
I am using Vue 3 Composition API.
This is what I am trying to do:
Food.vue Component
<script setup>
var food = "blueberry"
function changeFood () {
food = "cherry";
}
</script>
<template>
<div>{{food}}</div>
</template>
Page.vue
<script setup>
import { onMounted } from "vue";
import food from "#/components/Food.vue";
onMounted(async() => {
// I want to execute changeFood() from the imported component. How can I do this?
});
</script>
<template>
<food></food>
</template>
I know this can be done with page props, but that's not what Im trying to do. I am trying to execute a function.

You have to expose the method to the parent using defineExpose;
Food.vue
<script setup>
import { ref } from "vue";
const food = ref("blueberry");
const changeFood = () => {
food.value = "cherry";
};
defineExpose({ changeFood });
</script>
<template>
<div>{{food}}</div>
</template>
Page.vue
<script setup>
import { ref, onMounted } from "vue";
import food from "#/components/Food.vue";
const myFood = ref(null);
onMounted(async() => {
if (myFood.value) {
myFood.value.changeFood();
}
});
</script>
<template>
<food ref="myFood" />
</template>
Demo

Related

Vue3 updating values between components

I have a basic SPA with two child components, a header and a side menu (left drawer).
I wish the user to be able to click a button on the header component to call a function in the side menu component.
I understand I can use props to access a variable between parent & child components however how can I update a value between two sibling components?
Header
<q-btn dense flat round icon="menu" #click="toggleLeftDrawer" />
Left Drawer
import { ref } from 'vue'
export default {
setup () {
const leftDrawerOpen = ref(false)
return {
leftDrawerOpen,
toggleLeftDrawer () {
leftDrawerOpen.value = !leftDrawerOpen.value
}
}
}
}
Use global stores. Create a file
/store.js (you can obviously use any name)
Inside this file store/write the following code:-
import { reactivity } from 'vue'
export const global = reactive({
yourVariable: 'initialValue'
})
You can then import this variable and interact with it from anywhere and the change will be global. See the code below:
In header component:-
<script setup>
import { global } from './store.js'
const clicked = ()=> {
global.yourVariable = 'changed'
}
</script>
<template>
<button #click="clicked">
</button>
</template>
In leftDrawer Component:-
<script setup>
import { global } from './store.js'
</script>
<template>
<div>
{{ global.yourVariable }}
<!--You'll see the change:)-->
</div>
</template>
Then add these two in your main vue:-
<script setup>
import headerComponent from '...'
import leftDrawerComponent from '....'
//....
</script>
<template>
<div>
<header-component />
<left-drawer-component />
</div>
</template>

Render slot as v-html (Vue 3)

Goal
How to implement a component that renders an html string (eg fetched from a CMS) passed as a slot like this :
// app.vue
<script setup>
import MyComponent from "./MyComponent.vue"
const htmlStr = `not bold <b>bold</b>`
</script>
<template>
<MyComponent>{{htmlStr}}</MyComponent>
</template>
Explanation
To render an html string (eg fetch from a CMS) we can use v-html :
// app.vue
<script setup>
const htmlStr = `not bold <b>bold</b>`
</script>
<template>
<p v-html="htmlStr"></p>
</template>
Failed attempts
I have tried with no success :
// component.vue
<script>
import { h } from "vue";
export default {
setup(props, { slots }) {
return () =>
h("p", {
innerHTML: slots.default(),
});
},
};
</script>
Renders
[object Object]
Link to playground
Workaround with props
As a workaround, we can of course use props but it's verbose.
// app.vue
<template>
<MyComponent :value="htmlStr">{{htmlStr}}</MyComponent>
</template>
// component.vue
<template>
<p v-html="value"></p>
</template>
<script setup>
import { defineProps } from 'vue'
defineProps(['value'])
</script>
slots.default() returns an array of your passed slot elements, try to map that content and render it :
h("p", {
innerHTML: slots.default().map(el=>el.children).join(''),
});
Playground

How to use template refs in Nuxt 3

In Nuxt2 there were template $refs that you could access in <script> with this.$refs
I would like to know what is the Nuxt3 equivalent of this is.
I need this to access the innerText of an element. I am not allowed to use querySelector or getElementById etc.
This is the way we write code. I can give html elements ref="fooBar" but I can't access it with this.$refs.fooBar or even this.$refs.
<script setup lang="ts">
import { ref, computed } from 'vue';
const foo = ref('bar');
function fooBar() {
//Do stuff
}
</script>
<template>
//Html here
</template>
With Options API
<script>
export default {
mounted() {
console.log('input', this.$refs['my-cool-div'])
}
}
</script>
<template>
<div ref="my-cool-div">
hello there
</div>
</template>
With Composition API
<script setup>
const myCoolDiv = ref(null)
const clickMe = () => console.log(myCoolDiv)
</script>
<template>
<button #click="clickMe">show me the ref</button>
<div ref="myCoolDiv">
hello there
</div>
</template>

How to make vue3 import async dynamic component work?

I am a beginner using vue3.
We can use dynamic component like this:
<script setup>
import CommonLayout from "#/components/Layout/CommonLayout.vue";
</script>
<template>
<component :is="CommonLayout>
</component >
</template>
and I try to use dynamic component like this,but it is wrong:
export default {
CommonLayout: () => import("./CommonLayout.vue"),
EmptyLayout: () => import("./EmptyLayout.vue"),
HeaderLayout: () => import("./HeaderLayout.vue"),
};
<script setup>
import layouts from "#/components/Layout/index.js";
const { default: Layout } = await layouts["CommonLayout"]();
</script>
<template>
<Layout>
something
</Layout>
</template>
not error catch but the page show nothing.
and the Layout is the same with CommonLayout:
You need to use defineAsyncComponent
<script setup>
import { defineAsyncComponent } from 'vue'
const CommonLayout = defineAsyncComponent(() => import("./CommonLayout.vue"))
const EmptyLayout = defineAsyncComponent(() => import("./EmptyLayout.vue"))
const HeaderLayout = defineAsyncComponent(() => import("./HeaderLayout.vue"))
</script>
<template>
<component :is="CommonLayout></component>
<component :is="EmptyLayout></component>
<component :is="HeaderLayout></component>
</template>

Vue 3: component `:is` in for loop fails

I'm trying to loop over a list of component described by strings (I get the name of the component from another , like const componentTreeName = ["CompA", "CompA"].
My code is a simple as:
<script setup>
import CompA from './CompA.vue'
import { ref } from 'vue'
// I do NOT want to use [CompA, CompA] because my inputs are strings
const componentTreeName = ["CompA", "CompA"]
</script>
<template>
<h1>Demo</h1>
<template v-for="compName in componentTreeName">
<component :is="compName"></component>
</template>
</template>
Demo here
EDIT
I tried this with not much success.
Use resolveComponent() on the component name to look up the global component by name:
<script setup>
import { resolveComponent, markRaw } from 'vue'
const myGlobalComp = markRaw(resolveComponent('my-global-component'))
</script>
<template>
<component :is="myGlobalComp" />
<template>
demo 1
If you have a mix of locally and globally registered components, you can use a lookup for local components, and fall back to resolveComponent() for globals:
<script setup>
import LocalComponentA from '#/components/LocalComponentA.vue'
import LocalComponentB from '#/components/LocalComponentB.vue'
import { resolveComponent, markRaw } from 'vue'
const localComponents = {
LocalComponentA,
LocalComponentB,
}
const lookupComponent = name => {
const c = localComponents[name] ?? resolveComponent(name)
return markRaw(c)
}
const componentList = [
'GlobalComponentA',
'GlobalComponentB',
'LocalComponentA',
'LocalComponentB',
].map(lookupComponent)
</script>
<template>
<component :is="c" v-for="c in componentList" />
</template>
demo 2
Note: markRaw is used on the component definition because no reactivity is needed on it.
When using script setup, you need to reference the component and not the name or key.
To get it to work, I would use an object where the string can be used as a key to target the component from an object like this:
<script setup>
import CompA from './CompA.vue'
import { ref } from 'vue'
const components = {CompA};
// I do NOT want to use [CompA, CompA] because my inputs are strings
const componentTreeName = ["CompA", "CompA"]
</script>
<template>
<h1>Demo</h1>
<template v-for="compName in componentTreeName">
<component :is="components[compName]"></component>
</template>
</template>
To use a global component, you could assign components by pulling them from the app context. But this would require the app context to be available and the keys known.
example:
import { app } from '../MyApp.js'
const components = {
CompA: app.component('CompA')
}
I haven't tested this, but this might be worth a try to check with getCurrentInstance
import { ref,getCurrentInstance } from 'vue'
const components = getCurrentInstance().appContext.components;