1

I want to inject markup into existing components.

This is an easy example:

<!-- Arbitrary component X -->
<template>
  <div>
    <!-- I want a headline here -->
    foo!
  </div>
</template>

I know that I can achieve this by inserting a <slot/> and then using <X><h1>Hello world!</h1></X>. However, I want to do it dynamically without editing the original component.

So here's my idea using a higher-order-component:

import X from '~/components/X';
import injectHeadline from '~/hoc/injectHeadline.js';

export default {
  components: {
    X: injectHeadline(X, 'Hello world!')
  }
}

with

<!-- injectHeadline.js -->
export default (component, headline) => Vue.component({
    render(createElement) {
        let result = createElement(component);

        <!-- (*) somehow insert <h1>{{ headline }}</h1> here. How? -->

        return result;
    }
})

However, I had no luck manipulating the render result in (*). I tried fiddling with result.context.$children, but that leads nowhere. Any idea?

ThomasR
  • 1,067
  • 1
  • 11
  • 16
  • As a workaround, I found `export default (component, title) => ({ extends: component, mounted() { let titleElement = document.createElement('h1'); titleElement.textContent = title; this.$el.insertBefore(titleElement, this.$el.firstChild); } });`, but this works only on the client-side. I'd prefer the `render` phase. – ThomasR Jun 21 '19 at 06:36
  • Maybe have a look at [Vue.compile](https://vuejs.org/v2/api/#Vue-compile) function that compiles a template string into a render function. – Yom T. Jun 21 '19 at 06:44
  • @YomS. Thanks. I considered that. But I have trouble determining the original component's markup from within the `render` function. – ThomasR Jun 21 '19 at 06:52

2 Answers2

2

It's possible to use the same approach for template extension as in React, to modify a hierarchy of VNodes before they are rendered to DOM, as explained in this answer:

export default (WrappedComponent, headline) => Vue.component({
  extends: WrappedComponent,
  render(h) {
    const elements = this.$options.extends.render.call(this, h);
    elements.children.unshift(<h1>{headline}</h1>));
    return elements;
  }
});
Estus Flask
  • 206,104
  • 70
  • 425
  • 565
  • Thanks, exactly what I neded! For some reason it works only if I return a config object without `Vue.component`, so I ended up with `export default (WrappedComponent, headline) => ({ extends: WrappedComponent, render(createElement) { const elements = this.$options.extends.render.call(this, createElement); elements.children.unshift(createElement('h1', headline)); return elements; } }); ` – ThomasR Jun 21 '19 at 15:18
0

simple as that

<script>
export default {
  functional: true,
  render: function(createElement, context) {
    return createElement("div", context.slots().default);
  }
};
</script>

btw i set it to functional for performance (there is no need for vnode in this case)

in your code you could do it like this

let result = createElement(component,this.$slots.default);
eli chen
  • 653
  • 6
  • 17