Move child of shadow dom with slots out of the web component - javascript

I'm trying to move a child of a shadow dom of a web component outside it, and has been able to do it if that child has not slots inside.
Let me ilustrate with an example. I have the following 2 web components:
customElements.define('a-child', class extends HTMLElement {
constructor() {
super()
this.attachShadow({mode: 'open'})
this.shadowRoot.innerHTML = `
<slot></slot>
`
}
})
customElements.define('a-parent', class extends HTMLElement {
constructor() {
super()
this.attachShadow({mode: 'open'})
this.shadowRoot.innerHTML = `
<slot name="outside"></slot>
<a-child id=child>
<div>
<slot name="inside"></slot>
</div>
</a-child>
`
}
move() {
const child = this.shadowRoot.getElementById('child')
document.body.append(child)
}
})
And if I have the following HTML, I get what I want: even when slot inside is inside the a-child component in the Shadow DOM from a-parent, it shows up correctly.
<a-parent id="parent">
<div slot="outside">outside</div>
<div slot="inside">inside</div>
</a-parent>
If I run the method move from element with Id parent, the a-child element is moved to the body and I was expecting to see the div with text inside in the body, but it doesn't happen. It seems that the slots only work when they are inside their web component.
My question is, is it possible to move a child of a Shadow DOM containing slots, outside of its web component parent?

You are right. Slots only work when they are inside their enclosing Web Components. In other words, the slots should be physically present in the DOM tree under the web component node.
Further, it is possible to move the children of Shadow DOM but if they contain slots, then it won't work. It will just fail silently without any errors.
That is one of the reasons why it is annoying. Read more for similar question. Also, this twitter thread contains some more related information. In summary, you need to move slot elements outside the component for achieving perfect stacking context for creating components like dialogs, menus, drop-downs, etc.

Related

Use document in custom element (web component)

I have two separate custom element components and if I want to do something with the ComponentB's element inside of ComponentA, what is the best way to do it?
index.html
<body>
<componentA>
<div class="container">
<p>I am component A</p>
<p>abc</p>
</div>
</componentA>
<componentB>
<div class="container">
<p>I am component B</p>
<p></p> // Will set data between the p tag in componentA
</div>
</componentB>
</body>
componentA.js
...component template...
class ComponentA extends HTMLElement {
constructor() {
super();
this.attachShadow({mode: 'open'});
this.shadowRoot.appendChild(template.content.cloneNode(true));
}
connectedCallback() {
this.setInnerTextToComponentBElement();
}
setInnerTextToComponentBElement(){
// How to set componentB's second p tag innerText?
}
}
customElements.define('componentA', ComponentA );
I have thought of using document.querySelector to get the componentB element and go from there... but is this the best practice way to do it?
I would suggest NEVER tying two components together without allowing the developer using the components to provide a way to connect them.
Normally communications or interconnections between elements is handled by the parent. The only element that I know of that does this by itself is the <label> element. If this is the parent of an <input>, <select>, <button> or <textarea> then it will pass its focus on to the child element.
But to use it with a sibling element you have to set the for attribute of the <label> to be the id of the other field. And then the other field needs to have its id set.
I answered something like this here:
How to communicate between Web Components (native UI)?
Once you tie two components together these components are no longer usable by themselves unless you write extra code to allow separation.
Instead, either allow the components to dispatch events that the parent will receive and the parent will then pass values on to the other component. Or, follow the example of how <label for=""> works.

Access to DOM element with Vue method

I have a container who have inside many elements like h1, p etc.
This container have a background, and I want create method to move him by mouse coordinates. I have a problem with access to this element, because e.target show me elements inside container, not container who is binded element.
Alive any solution to share all functions between main component and child components? Because when i want get this method in anyone place now i must add :moveHero="moveHero" to every component and get this in prop array, so i want create this more globally in one place and share this to all childs. Now i think one solution for this is create .js file and import that to Vue.
You can add $ref to the container element like this: <div ref="container-ref">.
Then to access the container element in vue methods, just do this.$refs["container-ref"].
Explanation:
You can add ref attribute to any tag, even child components. It is parsed by vue only, browser ignores it.
If the ref is attached to a HTML tag, this.$refs[key] will return the corresponding DOM element.
If the ref is attached to a child component, this.$refs[key] will return the corresponding vue instance.

Angular2 : get hold of an element in template of a sibling component from a component

recently started Angular2, came across the below scenario
where need to access the element from a sibling component, but not from Parent Cmp. Thanks for looking at it
Example:
Let's say we have component A and component B which are on same
level.
Need the iframe element in templateA in ComponentB to Hide or
delete the element.
index.html
<component-A> </component-A>
<component-B> </component-B>
ComponentA.html
<div>
<iframe id="someIframe"></iframe>
</div>
ComponentB.html
<div>
<form id="someForm"></form>
</div>
#component
({
selector:'component-A',
templateUrl:'/componentA.html'
})
constructor() {
}
#component
({
selector:'component-B',
templateUrl:'/componentB.html'
})
constructor() {
//need to get hold of the iframe element to hide that.
}
You can use #ViewChild to get ahold of the sibling component. So your Component B class should look something like this;
import {Component,ViewChild,AfterViewInit} from 'angular2/core';
import {ComponentA} from './componentA';
#Component({
selector: 'component-B',
templateUrl: '/componentB.html'
})
export class ComponentB implements AfterViewInit{
#ViewChild(ComponentA)
child:ComponentA; //say get the first instance of ComponentA
ngAfterViewInit() {
console.log(this.child); //access the child here
}
}
Read more about #ViewChild here
There are couple of ways you can share data between siblings:
using a shared service: I had asked a similar question a few months back. Take a look here:
share data using service
Communicating using parent: You could create a parent component to these siblings and then sibling can communicate using this parent component. So your data transfer would happen as sibling1 -> parent -> sibling2. More details here: Component Interaction
I would certainly not want to access the element directly from component B, you'll want to leave them decoupled.
2 possible ways of solving this is:
Using a service which contains a toggle to hide or show the iframe.
Or you could use an event mechanism where you fire an event from
component B which component A listens to and it toggles the iframe
element accordingly.

How to attach react component to existing react instance from outside of react

I have a custom element and inside of it I want to use react and attach to parent instance, in order to whole stuff works as one.
// main.js
function App () {
return (
<div>
<h1>Hello World</h1>
<x-message>Hello World</x-message>
</div>
)
}
// x-message.js
class XMessage extends HTMLElement {
connectedCallback(){
this.__reactInternalInstance.moundChild(React.createElement('a',{}, ...))
}
}
I want to render another element and attach it to parent react context inside of the x-message element.
Here is an example.
This example works in chrome and the browsers which supports custom elements. as you can see it does not know anything about parent and parent context when renders child element.

What components view does ViewContainerRef refer to if specified as read parameter to `#ViewChildren`

I'm reading this article on #ViewChildren decorator and it says the following about read parameter:
ViewContainerRef — You need this token when you need to create
templates or components dynamically
#ViewChildren(AlertComponent, { read: ViewContainerRef }) alerts: QueryList<AlertComponent>
Is my understanding correct that it returns view containers references of child AlertComponent components so I can add content dynamically into these components view?
Whenever a compiler encounters { read: ViewContainerRef } for any element it creates a view container on this element. Any element can act as a view container. Each component host view can have many child embedded views which are checked before the component view child components/host views.
So when you use the following:
#ViewChildren(AlertComponent, { read: ViewContainerRef }) alerts;
you're basically saying that you want to create a view container on the element which is the host element for the AlertComponent component. This view container will house child views of the current component, not the AComponent. It's not different if you defined a view container on a div element. The view container is not related in any way to AComponent.
Also see this answer for some examples.

Categories