VueJS - Generating html string of a component in computed property - vue.js

I'm looking for a suggestion regarding a cleaner approach to generate a component data as html string and to pass it raw through the props of the component.
component-a.js
import componentB from './component-b'
computed: {
tooltipHTML() {
render "<componentB :name='user1'/>
}
}
I would prefer something similar to the above idea.

Generating HTML in a computed property and passing it as props to another component to be rendered in that component will not work.
What you are looking for is Slots
Since the complete code is not provided I guess you wanted to render <componentB :name='user1'/> inside another component( a tooltip component)
You would be doing it as follows using slots:
<tooltip-comp>
<componentB :name='user1'/>
</tooltip-comp>
In your tooltip component
//tooltip component
<template>
<div class="my-tooltip">
<p>my tooltip</p>
<slot></slot>
</div>
</template>

Related

VueJS 3: Access root HTML element in a slot

how do I reliably access the root HTML element in a slot? I tried slots.default()[0].el but its not consistent. I realized if the slot is a simple html, it is not null, which is great, but if it has some vue directives or components, it will be null. So how can I reliably get hold of the root HTML Element in a slot?
I found one possible solution: that is to have the slot content provider to explicitly set the element you want to reference to by providing a slot-prop method to invoke. And also since Vue 3 template supports multiple root elements, its not really reliable to assume that the slot will always have one root element.
Here is the example of the solution:
<template>
<slot :set-element="(el) => element = el"></slot>
</template>
<script setup lang="ts">
import { ref, watchEffect } from "vue";
const element = ref<Element | null>(null);
watchEffect(() => {
console.log(element.value);
});
</script>
In the usage of the component, inject the slot props and use the Function Refs syntax
<MyComponent v-slot="{ setElement }">
<div :ref="(el) => setElement(el)">...</div>
<div>...</div>
</MyComponent>
You could access a slot's root HTML element directly with the slot's vnode property and in your component script, you can using this.$refs.root.
Here is an example:
<template v-slot:[slotName]="{ vnode }">
<div ref="root">
<!-- your slot content goes here -->
</div>
</template>
mounted() {
const root = this.$refs.root as HTMLElement;
console.log(root);
}
EDIT
The official documentation for v-slot can be found here: https://vuejs.org/api/built-in-directives.html#v-slot
Instead, the official documentation for $refs can be found here: https://v3.vuejs.org/guide/composition-api-template-refs.html

How to change data in default layout from nested components in Nuxt.js

I have layout default.vue:
<template>
<div>
<Header></Header>
<div class="body-wrapper">
<Nuxt :numberThird="numberThird" />
</div>
<Footer></Footer>
</div>
</template>
<script>
export default {
data() {
return {
numberThird: 3
};
},
};
</script>
Here I am trying to pass prop numberThird.
I want to be able to change this value in the future through components that are deeply nested.
But there is a problem: my pages don't accept this prop (numberThird), they treat it as $parent.$attr.
The question is: can I somehow change this value through deeply nested child components?
As stated here: https://stackoverflow.com/a/67817642/8816585
Props and listeners are not the most friendly on either <nuxt> nor <nuxt-link>.
Kinda confirmed here by Alexander too: https://github.com/nuxt/nuxt.js/issues/8669#issuecomment-764006062
You better off using Vuex in this case, especially if you are aiming to something deep. Props drilling is not recommended most of the time.

Adding props on runtime to Vue component

I try to create a highly dynamic wizard as a component in Vue. It contains out of three components: the Wizard component itself, a Step component and a single form "MyForm" component. The form can be in edit mode or in read only mode depending on the current step of the wizard.
After some trial and error I finally managed to create such a component and it works. One problem that I struggled to solve was to pass the information if the form is in edit mode or not from the Step component to the child form component.
MyForm.vue
<template>
<form>
<div v-if="inEditMode"><i>Form is in edit mode</i></div>
<div v-else><i>Form is in read only mode</i></div>
</form>
</template>
<script>
import Vue from "vue";
export default Vue.extend({
props: ["inEditMode"]
// mixins: [wizardStepMixin],
});
</script>
Wizard.vue
<Step>
<MyForm/>
</Step>
Step.vue
<slot :isInEditMode="true"/>
Passing/setting a prop to a slot like I did above did not work (prop did not change).
My solution to set the prop isInEdit on the MyForm is to call a function prepareSlot in the Step component before mounting and updating the Step.
prepareSlot() {
this.$slots.default.forEach(element => {
if (!element.data) return
element.componentOptions.propsData = {
...element.componentOptions.propsData,
inEditMode: this.stepNr === this.currentStep
}
})
}
You can find the complete project on https://codesandbox.io/embed/mzr10wzk0j.
Is there a better way to archive that? Is it safe to do it that way?

Passing data from template to component

I have the following, which for my understanding should pass the value of html attribute to the #Prop with the same name however my console.log is always undefined. How is this accomplished?
import Vue from 'vue';
import { Component, Prop } from 'vue-property-decorator';
#Component({})
export default class RelayComponent extends Vue {
#Prop([String]) service: string;
constructor() {
super();
console.log(this.service);
...
HTML
<template>
<div service="expecting this value passed"></div>
</template>
<script src="./relay.ts"></script>
Vue props
Vue props are intended to pass data from a parent vue component or instance to a child vue component.
So you have a vue component, you set up a #Prop and then you get the prop for the html of the parent. Should you have a my-parent and my-child components, the my-parent template could be:
<template>
<my-child count="7"></my-child>
</template>
So a child component like this:
<template>
<div class="counter">{{count}}</div>
</template>
<script lang="ts">
import Vue from 'vue';
import { Component, Prop } from 'vue-property-decorator';
#Component({})
export default class myChild extends Vue {
#Prop() count: number;
}
</script>
Would get 7 as its count prop.
Now, in your case, there is only one component, and you're trying to setup the service variable of the component from the HTML. This is sort of weird because the point of Vue is to achieve declarative rendering from the component data: is the HTML who reacts to data changes, not your component who gets data from the HTML.
(Of course, you can also setup v-model and event listeners to make your components react to user input, but that's another story).
Basically, if I understood correctly what you want to do, your issue is that you're trying to get the service prop from the HTML of the very RelayComponent component.
Instead, you should setup the service prop in the component parent:
// Code of some parent component that renders the RelayComponent
<template>
<relay-component service="this would set the service prop as a string"></relay-component>
</template>
Only, when dealing with objects, you usually don't pass down a plain string, but a javascript object, and a service variable probably is an object, so changes are you're behind something like this:
<template>
<relay-component v-bind:service="serviceVariableInTheParentComponent"></relay-component>
</template>
Where the parent component has a service variable in its data.
 Constructor and lifehooks
Be wary about explicitly calling constructor in vue class components. If you modify the component state in the constructor, you can break the component.
Probably, you should consider to ever use the created() lifecycle hook instead of constructor() in every Vue component.

How to preserve custom component tag names in Vue.js

I'm using Vue's single-file component spec (*.vue) for custom components in my application. Together with rollup and rollup-plugin-vue, I have observed the output in the DOM, for custom components I have written, to be composed of the equivalent html elements.
For example:
component-a.vue
<template>
<span>Hello World</span>
</template>
<script>
export default { name: 'component-a' };
</script>
component-b.vue
<template>
<component-a></component-a>
</template>
<script>
import ComponentA from './path/to/component-a.vue';
export default { name: 'component-b', components: { ComponentA } };
</script>
The above example, if component-a is added to the Vue mount component, will render to a the sum of the two component's template contents in the DOM, which in this case is simply a span element:
<span>Hello World<span>
Is it possible to achieve a rendered output in the DOM like the snippet below, such that custom elements' templates are represented in the DOM by tags which preserve their tag names?
<component-b>
<component-a>
<span>Hello World</span>
</component-a>
</component-b>
Inside your component-a.vue you should be able to achieve that by including some html code within your <template> tag as follow
component-a.vue:
<template>
<customelement>
// Other stuff
</customelement>
</template>
In this way you will be able to call your component-a from anywhere in your app, and to render an element named customelement.
Ideally you should use this "trick" to render standard HTML5 elements, otherwise you might see some error in your vue app console. Let me know how it goes.
Referring to the Vuejs documentation
https://v2.vuejs.org/v2/guide/components.html#DOM-Template-Parsing-Caveats
It should be noted that this limitation does not apply if you are
using string templates from one of the following sources:
String templates (e.g. template: '...')
Single-file (.vue) components
<script type="text/x-template">
If you use Vuejs like the examples above you won't get the result you wanted. So if you render your components in other ways you should get the result you wanted.