How to use <component :is=""> in vue 3 script setup - javascript

I am using the experimental script setup to create a learn enviroment. I got a selfmade navigation bar with open a single component.
I am having trouble using the <component :is="" /> method. This method is described in the docs under component basics -> dynamic-components
In the Vue 3 Composition API, it works as expected:
<template>
<NavigationBar
#switchTab="changeTab"
:activeTab="tab"
/>
<component :is="tab" />
</template>
<script>
import { ref } from 'vue'
import NavigationBar from './components/NavigationBar.vue'
import TemplateSyntax from './components/TemplateSyntax.vue'
import DataPropsAndMethods from './components/DataPropsAndMethods.vue'
export default {
components: {
NavigationBar,
TemplateSyntax,
DataPropsAndMethods
},
setup () {
const tab = ref('DataPropsAndMethods')
function changeTab (newTab) {
tab.value = newTab
}
return {
changeTab,
tab
}
}
}
</script>
My approach with the script setup fails:
<template>
<NavigationBar
#switchTab="changeTab"
:activeTab="tab"
/>
<component :is="tab" />
</template>
<script setup>
import NavigationBar from './components/NavigationBar.vue'
import TemplateSyntax from './components/TemplateSyntax.vue'
import DataPropsAndMethods from './components/DataPropsAndMethods.vue'
import { ref } from 'vue'
const tab = ref('DataPropsAndMethods')
function changeTab (newTab) {
tab.value = newTab
}
</script>
do you got any idea how to solve this with the script setup method?

It seems with <script setup>, tab needs to reference the component definition itself instead of the component name.
To reference the component definition, which does not need reactivity, use markRaw() before setting tab.value:
<script setup>
import DataPropsAndMethods from './components/DataPropsAndMethods.vue'
import { ref, markRaw } from 'vue'
const tab = ref(null)
changeTab(DataPropsAndMethods)
// newTab: component definition (not a string)
function changeTab (newTab) {
tab.value = markRaw(newTab)
}
</script>
demo 1
If you need to pass the component name to changeTab(), you could use a lookup:
<script setup>
import DataPropsAndMethods from './components/DataPropsAndMethods.vue'
import { ref, markRaw } from 'vue'
const tab = ref(null)
changeTab('DataPropsAndMethods')
// newTab: component name (string)
function changeTab (newTab) {
const lookup = {
DataPropsAndMethods,
/* ...other component definitions */
}
tab.value = markRaw(lookup[newTab])
}
</script>
demo 2
Tested with Vue 3.0.9 setup with Vue CLI 5.0.0-alpha.8

Related

How to add resize event handler to my vue code?

I am new to Vue. I am a react developer trying to update some legacy code from my work.
What I want to do is I want to have a screen resize event handler in my component.
I found some helpful code pen https://codepen.io/sandrarodgers/pen/porZbxW
The thing is I don't know how to add those in my existing component.
To give you the general idea. My component already got two script tags
When I try to copy paste from codeine it give me some error on this keyword
Component
<template>
<div :style="styles">
...
</div>
</template>
<script lang="ts">
import { defineComponent } from 'vue';
import MultipleChoiceField from '../fields/MultipleChoiceField.vue';
import NumberField from '../fields/NumberField.vue';
export default defineComponent({
name: 'Questionnaire',
components: {
MultipleChoiceField,
NumberField,
},
});
</script>
<script setup lang="ts">
import {
defineProps,
defineEmits,
computed,
nextTick,
onMounted,
ref,
toRefs,
watch,
unref,
} from 'vue';
import {
handleQuestionLogicCalculator,
} from '../../lib/main';
import { typeformMarkdownToHtml } from '../../lib/processTypeformMarkdown';
import {
PrefilledAdditionalParameters,
QuestionnaireChoice,
} from '../../lib/types';
import localize from '../../lib/localization';
const props = defineProps<{
additions: PrefilledAdditionalParameters;
questionnaireConfig: QuestionnaireConfig;
styles: QuestionnaireStyles;
submitAnswers: ShowThankYouFunction;
showwelcomeScreen: () => void;
goBack: () => void;
}>();
const emit = defineEmits(['latestQuestionnaireIndex']);
//MORE CODE HERE
</script>

Dynamically create a component in Vue JS

I need to create a component in Vue JS dynamically on click and then route to that component. I am using Vue 3. Everything needs to happen in one click.
My code looks something like this
methods:{
routerClick(value){
console.log("number is "+value)
this.$router.push({path:'New', name:'New', component: ()=>Vue.component('New')})
}
},
I do not need to move a component that is already created. I want to create a component inside this method and then route to the component using this router. Please, any suggestions will be highly appreciated.
Below is a simplistic solution that works (I'm not an expert in Vue 3).
The main point is to use addRoute before pushing to it, because you cannot specify the route component when pushing to a route.
Here is the codesandbox with the working solution.
<template>
<router-link to="/">Home</router-link>
<button #click="createComponent">Create Component</button>
<router-view></router-view>
</template>
<script>
import { getCurrentInstance } from "vue";
import { useRouter } from "vue-router";
export default {
name: "App",
setup() {
const app = getCurrentInstance().appContext.app;
const router = useRouter();
const createComponent = () => {
// Check if the component has been alreadey registered
if (!app.component("NewComponent")) {
app.component("NewComponent", {
name: "NewComponent",
template: `<div>This is a new component</div>`
});
}
const newComponent = app.component("NewComponent");
// Adding a new route to the new component
router.addRoute({ path: "/new", component: newComponent });
router.push("/new");
};
return {
createComponent,
};
},
};
</script>

onUpdated is not called with vuejs 3 with render function

I am facing a problem using the onUpdated lifecycle hook in Vuejs3 Composition API.
It is not called when a reactive value is updated.
I have reproduced the issue with a very simple app.
It has one child component:
<script setup lang="ts">
import { h, ref, onUpdated } from 'vue'
const open = ref(false)
const toggle = () => {
open.value = !open.value
}
defineExpose({ toggle })
onUpdated(() => {
console.log("Updated")
})
const render = () =>
open.value ? h(
'DIV',
"Child Component"
) :
null
</script>
<template>
<render />
</template>
Then this component is used by the app:
<script setup lang="ts">
import { ref } from 'vue'
import Child from './components/Child.vue'
const menu = ref(null)
</script>
<template>
<main>
<button #click="menu.toggle()">Click Me</button>
<Child ref="menu" />
</main>
</template>
<style>
</style>
But when the button is clicked in the app, although the "Child Component" text is shown, proving that the render function is called, the onUpdated callback is not executed.
This must have something to do with the way the render function is called, or the conditional rendering because if I use v-if in a template instead, it works fine. But in my case, I do need an explicit render function.
Can anyone help?
This is probably a bug in <script setup>, as that same code works in setup().
A workaround is to switch to setup() if you need to use onUpdated():
<script lang="ts">
import { h, ref, onUpdated } from 'vue'
export default {
setup(props, { expose }) {
const open = ref(false)
const toggle = () => {
open.value = !open.value
}
expose({ toggle })
onUpdated(() => {
console.log("Updated")
})
const render = () => open.value ? h('DIV', "Child Component") : null
return render
}
}
</script>
demo

How to add vue3-openlayers plugin to nuxt

I have the following main.ts file in Vue3:
import { createApp } from "vue"
import App from "./App.vue";
//How to do this in nuxt3?
import OpenLayersMap from "vue3-openlayers";
import "vue3-openlayers/dist/vue3-openlayers.css";
const app = createApp(App);
//How to do this in nuxt3?
app.use(OpenLayersMap);
app.mount("#app");
How can I add the vue3-openlayers plugin to nuxt3?
To auto-install a Vue plugin in Nuxt 3, create a .js/.ts file under <projectDir>/plugins/ (create the directory if needed) with the following boilerplate:
// plugins/my-plugin.js
import { defineNuxtPlugin } from '#app'
export default defineNuxtPlugin(nuxtApp => {
nuxtApp.vueApp.use(/* MyPlugin */)
})
Since vue3-openlayers depends on window, the plugin can only be installed client side, so use the .client.js extension.
To load vue3-openlayers client side, the plugin file would look like this:
// plugins/vue3-openlayers.client.js
import { defineNuxtPlugin } from '#app'
import OpenLayers from 'vue3-openlayers'
export default defineNuxtPlugin(nuxtApp => {
nuxtApp.vueApp.use(OpenLayers)
})
Create <projectDir>/components/MyMap.vue with the following example content from the vue3-openlayers docs:
// components/MyMap.vue
<script setup>
import { ref } from 'vue'
const center = ref([40, 40])
const projection = ref('EPSG:4326')
const zoom = ref(8)
const rotation = ref(0)
</script>
<template>
<ol-map :loadTilesWhileAnimating="true" :loadTilesWhileInteracting="true" style="height:400px">
<ol-view :center="center" :rotation="rotation" :zoom="zoom"
:projection="projection" />
<ol-tile-layer>
<ol-source-osm />
</ol-tile-layer>
</ol-map>
</template>
<style scoped>
#import 'vue3-openlayers/dist/vue3-openlayers.css';
</style>
We only want to render MyMap on the client because the plugin is only client-side, so use the <ClientOnly> component as a wrapper:
// app.vue
<template>
<ClientOnly>
<MyMap />
<template #fallback> Loading map... </template>
</ClientOnly>
</template>
demo

Vue 3: How to pass data from component to App.vue when there is a router-view in between?

I have the following structure:
src
components
Footer.vue
views
Page.vue
App.vue
I would like to be able to access the 'message' variable in App.vue, but I can´t figure out how to do it when Footer.vue is not a direct child of App.vue (but child of Page.Vue which - via the router - is child App.vue).
What do I need to add in my files? There are now as follows - and (of course) no message appears in App.vue:
//App.vue
<template>
  <p>Message is: {{ message }}</p>
  <router-view />
</template>
<style lang="scss">
#import "./_scss/main.scss";
</style>
.
//Page.vue
<template>
  <Footer/>
</template>
<script>
import Footer from '#/components/Footer.vue'
export default {
  components: {
    Footer
  }
}
</script>
.
//Footer.vue
<template>
<input v-model="message" placeholder="edit me">
<p>Message is: {{ message }}</p>
</template>
<script>
export default {
    data() {
        return {
            message: ''
        }
    }
}
</script>
Maybe Composition API and ES6 modules?
#/compositions/composition.js
import { ref } from 'vue'
const message = ref('test');
export const useComposition = function() {
// other functions, for example to mutate message ref
return {
message,
// ...
}
}
And now you import your composition in the components that need to access message:
// Footer.vue, App.vue
<script>
import { defineComponent } from 'vue'
import { useComposition } from '#/compositions/composition'
export default defineComponent({
setup() {
const { message } = useComposition();
return { // make it available in <template>
message
}
},
})
</script>
If you want to quickly get started with Composition API, see this.

Categories

Resources