Reputation: 422
So i have a vue
instance with a globally registered component in a legacy project. Since it's legacy, it mainly uses jsrender
to render html after some ajax requests.
But even if the component is already registered in the vue instance, vue does not render the new component instance after that data fetch, when component gets inserted as raw html <component-a></component-a>
.
How do I make vue render this?
I need this to work to be able to gradually inject VueJS into the legacy project
Further explanation:
I have this html blocks (jsrender
templates) like below on the page from the initial page request
<script id="some-name-tmpl" type="text/x-jsrender">
{{ if var1 == 0 }}
<div> {{ variable }} etc...</div>
{{/if}}
<my-vue-component></my-vue-component> // I need this to get rendered as a component
</script>
They get rendered by jsrender
library when some data come from the backend and in JS some render methods of jsrender
got called along with the data, so I get something like this:
<div> x etc...</div>
<my-vue-component></my-vue-component>
But the component (<my-vue-component></my-vue-component>
) gets inserted just as raw html, though it's inside of the #app
element on which vuejs
instance
is mounted, but it gets inserted after the vue instance is already initialized, so Vue
does not render this component
So I need to force Vue
to scan the page html again as on mount()
to render this component. Or I will have to migrate all the templates and data from jsrender
lib to Vue
in once and it's a lot of work
Upvotes: 5
Views: 14476
Reputation: 21
You can use dynamic components in conjunction with the built-in is attribute as a template. This will let vue render raw html along with rendering vue components.
<component :is="{ template: `${html}` }" />
then in your computed, return whatever html block you want that has the globally mounted vue component:
computed: {
html() {
return `<div> x etc...</div><my-vue-component></my-vue-component>`
}
}
using this method you will need to import:
import { createApp } from 'vue/dist/vue.esm-bundler'
This gives you the ability to use runtime template compilation in your app.
Upvotes: 2
Reputation: 3543
If I understand it right, essentially, you are trying to either render a component whose template is not known statically, or a component whose definition is not known statically. In other words, you want the template, or the component itself to be the "data" so that it can be fetched remotely/constructed dynamically at runtime.
This is achievable using dynamic component (the <component>
tag). The magic is the second bullet point in the document:
the value passed to :is can contain either:
- the name string of a registered component, OR
- the actual imported component object
Except that you will not import the component, you just create it on the fly. Vue component, surprising to most of people, is actually pretty simple - it is just a plain js object (in other words, the component definition is merely a spec).
In your case, one way of doing it is to define a DynamicComponent
to accept remote template as a string property (let's call it layout
), and this component can then create an anonymous component on the fly based on the given layout.
In the below example, two existing components are registered globally: DynamicComponent
and MyComponent
. The app is not rendering MyComponent
at the moment. You can type your jsrender-rendered layout as string into the text area, say
<my-component msg="hello world">
<button>I can even accept slot</button>
</my-component>
and click the button Render It
const App = {
template: `
<div style="display: flex; flex-flow: column">
<h1>App</h1>
<textarea v-model="text"/>
<button @click="renderIt"> Render It </button>
<dynamic-component :layout="layout" />
</div>`,
data() {
return { title: "App", text: "", layout: "" };
},
methods: {
renderIt() {
this.layout = this.text;
}
}
}
const MyComponent = {
props: ["msg"],
template: `
<h1>Hello from the MyComponent.vue</h1>
<div>{{ msg }}</div>
<slot></slot>
`,
};
const DynamicComponent = {
props: ["layout"],
template: `<component :is="definition" v-if="!!layout" />`,
computed: {
definition() {
return { template: this.layout };
},
}
};
const app = Vue.createApp(App);
app.component("my-component", MyComponent);
app.component("dynamic-component", DynamicComponent);
app.mount("#app");
<script src="https://unpkg.com/vue@3/dist/vue.global.prod.js"></script>
<div id="app"></div>
Obviously you still might have refactor your code a little bit, e.g. you probably need to figure out a way to know when jsrender has completed its part of the job. Even if there might not be a way to directly ask jsrender to render into a string, you can always just grab the innerHTML/outterHTML and forward it to the DynamicComponent.
Alternatively, Vue 3 root app can be mounted with dom template. E.g. it can take the DOM subtree rooted at the designated mounting point as template. This actually feels more ideal for your case, but might involve a bigger refactoring as you will break your entire application into multiple Vue apps so that you can imperatively mount a new Vue app instance when needed.
Upvotes: 2
Reputation: 1016
As I understand you need to render some Vue components after ajax calls and it seems they are out of Vue scope. There are 3 solutions for working with dynamic components, third one may be a solution for your case becuase it works out of Vue.
v-html
directive, it's prebuilt feature that you can render HTML string content under an dom element.<div v-html="htmlStringConent" />
<component :is="href ? 'a' : 'registeredComp'"></component>
jsrenderer
scope automatically.import { defineCustomElement } from 'vue'
const MyVueElement = defineCustomElement({
// normal Vue component options here
props: {},
emits: {},
template: `...`,
// defineCustomElement only: CSS to be injected into shadow root
styles: [`/* inlined css */`]
})
// Register the custom element.
// After registration, all `<component-a>` tags
// on the page will be upgraded.
customElements.define('component-a', MyVueElement)
then you can use your custom element anywhere the document tree
<component-a></component-a>
Hope it will help you to resolve your problem.
Upvotes: 4