question-mark
Stuck on an issue?

Lightrun Answers was designed to reduce the constant googling that comes with debugging 3rd party libraries. It collects links to all the places you might be looking at while hunting down a tough bug.

And, if you’re still stuck at the end, we’re happy to hop on a call to see how we can help out.

Problems with slots in wrapped components

See original GitHub issue

I have a simple Vue component with a default slot. I create an AngularJs wrapper using createVueComponent and then use the wrapper in AngularJS app. A simplified version of my setup is shown below.

const customLink = Vue.component('CustomLink', {
  template: '#custom-link',
})

angular.module('app', ['ngVue']).directive(
  'ngCustomLink',
  createVueComponent => createVueComponent(customLink),
);

angular.element(() => {
  angular.bootstrap(document.getElementById('app'), ['app']);
});
<div id="app">
  <p>
    No children (no text): <ng-custom-link></ng-custom-link>  
  </p>
  <p>
    Expected to see the default text: <i>Default Text</i>
  </p>
  <hr>

  <p>
    Text child (fails): <ng-custom-link>Raw text</ng-custom-link>
  </p>
  <p>
    Expected to see: <i>Raw text</i>
  </p>
  <hr>

  <p>
    Span child (ok): <ng-custom-link><span>span text</span></ng-custom-link>
  </p>
  <p>
    Expected to see: <i>span text</i>
  </p>
</div>

<template id="custom-link" type="x-template">
  <a href="#">
    <slot>Default Text</slot>
  </a>
</template>

The setup is available at https://jsfiddle.net/nqr876os/5/

There are 3 problems:

  1. When the AngularJS wrapper has a single child that is a text Node, an exception is thrown and the component is not rendered at all.
  2. When the AngularJS wrapper has no children, the default slot of the original Vue component is not displayed.
  3. When the AngularJS wrapper has multiple children, only the first one is displayed.
  • VueJS is v2.6.6
  • Angular is 1.6.6
  • ngVue is 1.6.0

Issue Analytics

  • State:open
  • Created 5 years ago
  • Reactions:3
  • Comments:9 (6 by maintainers)

github_iconTop GitHub Comments

1reaction
dorayxcommented, Apr 25, 2019

I feel sorry that we will not fix this bug and <slot/> has to be deprecated because your refactored code will get harder & harder to develop, test and debug.

With <slot/> the vue components have to take care of the angular runtime and eventually those will be tightly coupled.

If you have to preserve the angular code in the vue components perhaps you could use web components as a bridge between these two runtimes 😃

1reaction
SmilingJoecommented, Apr 22, 2019

So found a quick solution to this, at least when it comes to passing plain text inside of the component references tags.

Wrap your plain text content in the template where you reference the component with <span></span> tags. This will prevent the error from coming up, and cleanly pass the content through into the slot.

However, this still does not fix the issue where default content assigned into the slot does not appear, when not passed in from the template component reference.

Possible long term solution? After playing around with the jsfiddle created by korya above, I think a workable solution to this might be to first wrap whatever contents is found in jqElement[0].innerHTML with <span></span> tags first, then run it through the compiler. The extraneous span tags can then be removed from the resulting HTML before it is injected into the DOM.

Reason: This is caused by how AngularJS/jqLite processes bare strings without any wrapping tags around them.

It starts out with this line: https://github.com/ngVue/ngVue/blob/master/src/angular/ngVueLinker.js#L34, that attempts to have AngularJS $compile whatever the passed content is.

const html = $compile(jqElement[0].innerHTML)(scope)

This in turn is sent by Angular to the JQLite constructor, which attempts to parse it (from the angular.js file):

function JQLite(element) {
  if (element instanceof JQLite) {
    return element;
  }

  var argIsString;

  if (isString(element)) {
    element = trim(element);
    argIsString = true;      // <--- [DEBUG] This gets set to true
  }
  if (!(this instanceof JQLite)) {
    // [DEBUG] As argIsString is true, and the first character is not '<'... it throws an error
    if (argIsString && element.charAt(0) !== '<') {
      throw jqLiteMinErr('nosel', 'Looking up elements via selectors is not supported by jqLite! See: http://docs.angularjs.org/api/angular.element');
    }
    return new JQLite(element);
  }
Read more comments on GitHub >

github_iconTop Results From Across the Web

Building Component Slots in React | Articles - Sandro Roth
Slots are an way to pass elements to a component and let them render ... It then wraps the header and footer in...
Read more >
Vue - how to pass down slots inside wrapper component?
I ran into this issue when wanting to wrap the Kendo UI Grid component and exposing its cell template slots. – incutonez. Jul...
Read more >
Using templates and slots - Web Components | MDN
This article explains how you can use the and elements to create a flexible ... The <template> wraps the named slots in a...
Read more >
Shadow DOM slots, composition
What if the outer code wants to add/remove menu items dynamically? The browser monitors slots and updates the rendering if slotted elements are ......
Read more >
A comprehensive guide to Svelte components with slots
In the code above, we created a Card component. The slot component allows us to pass child data and content to the Card...
Read more >

github_iconTop Related Medium Post

No results found

github_iconTop Related StackOverflow Question

No results found

github_iconTroubleshoot Live Code

Lightrun enables developers to add logs, metrics and snapshots to live code - no restarts or redeploys required.
Start Free

github_iconTop Related Reddit Thread

No results found

github_iconTop Related Hackernoon Post

No results found

github_iconTop Related Tweet

No results found

github_iconTop Related Dev.to Post

No results found

github_iconTop Related Hashnode Post

No results found