How to inject template using slot with data-binding in Polymer2
Asked Answered
M

1

6

I'd like to inject a rendering template from a parent component to a child component using <slot> insertion points. The injected template contains data-binding on a property of the child component (my-child.data in this case).

<dom-module id="my-parent">
  <template>
    <my-child>
      <template>
        <div>Child's data property: [[data]]</div>
      </template>
    </my-child>
  </template>
  ...

The rendering child component basically looks like this:

<dom-module id="my-child">
  <template>
    <header></header>
    <slot></slot>
    <footer></footer>
  </template>

  <script>
    class MyChild extends Polymer.Element {
      static get is() { return 'my-child'; }
      static get properties() {
        return {
          data: { ... }
        };
      }
      ...

I'm not sure whether this is possible at all with Polymer2. Vue2 has a concept called "scoped slot" to achieve this. Thanks in advance for any feedback!

Miserere answered 7/6, 2017 at 12:12 Comment(3)
I think this is not possible since the [[data]] within the <my-parent> component template is always referred to itself and not the <my child> element. Also I think there isn't a "scoped slot" Vue-like in Polymer 2. Why can't you print the [[data]] directly inside your child component?Impetus
Thanks for the feedback. The reason for injecting a template is, that [[data]] can be wrapped by various tags, e.g. <strong>[[data]]</strong> or <code>[[data]]</code> and I wanted to avoid distinct custom elements for each possibility or having dom-if blocks in the my-child component being based on input properties to that component. In Polymer's legacy layer I still find templatizer-behavior which seems to be deprecated. However I did not find any documentation about migration from Polymer 1.x to Polymer 2.x for template stamping.Miserere
This is also possible in Polymer. It is true, that the bindings are always scoped to the current scope - but you can use a template and stamp it in a different scope. Your my-parent element actually has the contents of the my-child inside a template element, which means you only need to stamp the template inside your my-child.Fante
F
8

Data binding is by default tied within the current scope of the binding. If you wish to change the scope, you must put your markup inside a <template> tag and stamp in it inside a different scope.

Your HTML code in the question is already OK - you actually wrap the light DOM inside a <template>, but you then use that <template> incorrectly. You must not use <slot>, but must stamp that template manually and insert it somewhere inside the my-child element's shadow DOM.

Here you have a working demo on how to achieve this: http://jsbin.com/loqecucaga/1/edit?html,console,output

I have even added the data property binding to an input element in order to demonstrate that property changes also affect the stamped template.

The stamping is relatively simple and is done inside the connectedCallback method:

var template = this.querySelector('template');
this.__instance = this._stampTemplate(template);
this.$.content.appendChild(this.__instance);

The stamped template is put inside a placeholder div element, which you put somewhere inside the my-child's template:

<div id="content"></div>

To sum up, here is the full code from the demo:

<link href="polymer/polymer-element.html" rel="import"/>
<link href="polymer/lib/mixins/template-stamp.html" rel="import"/>

<dom-module id="my-parent">
  <template>
    <my-child>
      <template>
        <div>Child's data property: [[data]]</div>
      </template>
    </my-child>
  </template>

  <script>
    class MyParent extends Polymer.Element {
      static get is() { return 'my-parent'; }
    }

    window.customElements.define(MyParent.is, MyParent);
  </script>
</dom-module>

<dom-module id="my-child">
  <template>
    <header>Header</header>
    <div id="content"></div>
    <footer>Footer</footer>
    <input type="text" value="{{data::input}}" />
  </template>

  <script>
    class MyChild extends Polymer.TemplateStamp(Polymer.Element) {
      static get is() { return 'my-child'; }
      static get properties() {
        return {
          data: {
            type: String,
            value: 'Hello, World!'
          },
        };
      }

      connectedCallback() {
        super.connectedCallback();

        var template = this.querySelector('template');
        this.__instance = this._stampTemplate(template);
        this.$.content.appendChild(this.__instance);
      }
    }

    window.customElements.define(MyChild.is, MyChild);
  </script>
</dom-module>

<my-parent></my-parent>
Fante answered 9/6, 2017 at 18:30 Comment(1)
Thanks for the explanation, which sounds reasonable not to fiddle around with the shadow DOM of <slot> directly. Just tested, works like a charm with data-binding - perfect!Miserere

© 2022 - 2024 — McMap. All rights reserved.