How to send props from parent to child with LitElement - javascript

Can someone show me some simple example about sending props from parent class to child? What is the problem:
parent component:
import { LitElement, html, css } from 'lit-element';
import './child.js';
class Parent extends LitElement {
constructor() {
super()
this.message = "hello world"
}
render() {
return html `<child-component message=${this.message}></child-component>` //how to get this props in child component?
}
}
customElements.define('parent-component', Parent);
and child component:
import { LitElement, html, css } from 'lit-element';
class Child extends LitElement {
...
render() {
return html `<p>${message from parent, but how}</p>` //message props should go to constructor? to render method as argument? how?
}
}
}
customElements.define('child-component', Child);

ok, I found solution. If I want to define properties in Parent class I have to add dot.
Reference: https://lit-element.polymer-project.org/guide/properties
render() {
return html `<child-component .message=${this.message}></child-component>`
}
So now everything is working.
And full example:
parent component:
import { LitElement, html, css } from 'lit-element';
import './child.js';
class Parent extends LitElement {
constructor() {
super()
this.message = "hello world"
}
render() {
return html `<child-component .message=${this.message}></child-component>`
}
}
customElements.define('parent-component', Parent);
child component:
import { LitElement, html, css } from 'lit-element';
class Child extends LitElement {
...
render() {
return html `<p>${this.message}</p>` //this.message have "hello world" value now
}
}
customElements.define('child-component', Child);

Make it much standard.
In your child component., set properties to listen to message.
import { LitElement, html, css } from 'lit-element';
class Child extends LitElement {
static get properties(){
return{
message: {
type: String,
}
}
}
constructor(){
super();
console.log(message);
}
render() {
return html `<p>${this.message}</p>` //this.message have "hello world" value now
}
}
customElements.define('child-component', Child);

In children to load property from parent component:
#property({type: Number}) propertyChild: number;
constructor() {
super();
this.propertyChild = (this.parentElement as TypeParent).propertyParent;
}
In parent to update properties on children:
override updated(): void {
this.childNodes.forEach(node=>{
const nodeType = node as TypeChildren;
if(nodeType.property) {
nodeType.propertyChild = this.propertyParent;
}
})
}

Related

Lit reactive controllers fail to requestUpdate in nested components

I want to create a basic state management using Lit reactive controllers.
The purpose is to share property values accross the application.
The issue occurs when a controller is attached to a view and to a component nested in the view. When value inside controller changes, the value in the view gets updated, but not in the nested component.
Example:
state.js contains the store logic. A view access the store to create a value and show state value. Nested components also show state value.
state.js
export class StateController {
static get properties() {
return {
state: { type: Object },
host: { type: Object }
}
}
constructor(host) {
// Store a reference to the host
this.host = host;
this.state = {};
// Register for lifecycle updates
host.addController(this);
}
_setStoreValue(property, val) {
this.state[property] = val;
this.host.requestUpdate();
}
}
component.js
import { LitElement, html } from 'lit';
import { StateController } from '../state.js';
export class TestComponent extends LitElement {
static get properties() {
return {
stateCtrl: { type: Object },
state: { type: Object },
};
}
constructor() {
super();
this.stateCtrl = new StateController(this);
this.state = this.stateCtrl.state
}
render() {
return html` Value in component: ${this.state?.test} `;
}
}
customElements.define('test-component', TestComponent);
view.js
import { LitElement, html } from 'lit';
import { StateController } from '../state.js';
import './test-component.js';
export class MonTodo extends LitElement {
static get properties() {
return {
stateCtrl: { type: Object },
state: { type: Object },
};
}
constructor() {
super();
this.stateCtrl = new StateController(this);
this.state=this.stateCtrl.state
}
render() {
return html`
<button #click=${() => this.setValueTest()}>Set value to 3</button>
Value in view: ${this.state?.test}
<h3> Component 1</h3>
<test-component></test-component>
<h3> Component 2</h3>
<test-component></test-component>
`;
}
setValueTest() {
this.stateCtrl._setStoreValue("test", 3)
}
}
customElements.define('mon-todo', MonTodo);
A button click in view.js updates this.state.test in view.js but not in component.js
Since you create a new StateController in both MonTodo and TestComponent, they are two different instances of the StateController which only have their specific component as host.
So the StateController in MonTodo only has MonTodo as a host and only updates that and not TestComponent.
You would need to share one controller with both components and call requestUpdate on both.

Communcation between Web Components

I am building a project with web components and vanilla javascript.
I have a component/module called meal.module - It is the parent component of the components meal-list and meal-search.
meal-list displays multiple meals from an api.
meal-search contains an input field and seachterm as attribute.
meal.module.js
export default class MealModule extends HTMLElement {
connectedCallback() {
this.innerHTML = '<mp-meal-search searchterm=""></mp-meal-search> ' +
'<mp-meal-list></mp-meal-list> ' +
}
}
if (!customElements.get('mp-meal-module')) {
customElements.define('mp-meal-module', EssenModule);
}
meal-list.component
export default class MealListComponent extends HTMLElement {
connectedCallback() {
this.attachShadow({mode: 'open'});
this.shadowRoot.innerHTML = Template.render();
// Renders the meals from api into the template
this.getMeals();
}
(....) more code
}
if (!customElements.get('mp-meal-list')) {
customElements.define('mp-meal-list', MealListComponent);
}
meal-search.component
export default class MealSearchComponent extends HTMLElement {
static get observedAttributes() {
return ['searchterm'];
}
attributeChangedCallback(name, oldVal, newVal) {
if (name === 'searchterm') {
this.doSearch();
}
}
set searchTerm(val) {
this.setAttribute('searchterm', val)
}
get searchTerm() {
return this.getAttribute('searchterm');
}
constructor() {
super();
this.attachShadow({mode: 'open'});
this.shadowRoot.innerHTML = '<input type="text" id="searchterm" value=""/>'
this.shadowRoot.addEventListener('change', (event) =>
this.searchTerm = event.target.value
)
}
doSearch() {
// send signal to MealListComponent for search
}
}
if (!customElements.get('mp-meal-search')) {
customElements.define('mp-meal-search', MealSearchComponent);
}
In the seach-component the SearchTerm is configured as Attribute. Everytime the input field gets changed, the attribute also changes.
Now I want to implement a "searchMeal" function, that always triggers when the attribute in MealSearchComponent changes.
I already tried to import the MealSearchComponent into the MealListComponent. But it does seem to break the rule of components, not having any dependencies.
JavaScript from the outside of the shadow DOM can access the shadow DOM via the element.ShadowRoot property.

How to access this.props in child class in function that overrides parent's function

I want to use this.props.childName in child function that is defined in the parent function.
But it's TypeScript compile error (Property 'name' does not exist...)
If I use this.props.parentName, it's ok.
How can I access this.props of child class?
interface Prop<T> {
parentName: string
}
class Parent<T> extends React.Component<Prop<T>, State<T>> {
constructor(props: Prop<T>) {
super(props)
}
printName() {}
}
interface PropChildren<T> {
childName: string
}
class Child<T> extends Parent<string> {
constructor(props: PropChildren<T>) {
super(props)
}
printName() {
console.log(this.props.childName) // here I want to use children prop but compile error
}
}
Your child component extends the parent component, and the type of props in the parent is Prop<T>, which contains only the property parentName.
In order to have PropChildren as the type of props in the child component you should declare it as:
class Child<T> extends React.Component< PropChildren<T>, State<T>> {
// ...
}
By the way, you don't need to make your props interfaces generic (with <T>). The generics are used only when the interface can be used in different contexts with different data types.
Based on your comment, here is an example of how you can share the behavior of the parent with the child, but still being able to define a different data type for the child's props:
interface PropParent {
parentName: string
}
class Parent<TProp extends PropParent> extends React.Component<TProp, State> {
constructor(props: TProp) {
super(props)
}
printName() {}
}
interface PropChildren extends PropParent {
childName: string
}
class Child<T> extends Parent<PropChildren> {
constructor(props: PropChildren) {
super(props)
}
printName() {
console.log(this.props.childName)
}
}
first, you don't need any Generics in interface unless you need to use it in different places.
second, class Child should also extend from React.Component not from its parent.
so here is what might be a better code
import React from 'react'
interface IParentProps {
readonly parentName: string;
readonly children?: JSX.Element
}
interface IPropsChild {
readonly childName: string;
}
class Parent extends React.Component<IParentProps> {
constructor(props: IParentProps) {
super(props)
}
printName = () => {
}
render() {
return <Child childName={this.props.parentName} />
}
}
class Child extends React.Component<IPropsChild> {
constructor(props:IPropsChild) {
super(props)
}
printName = () => {
console.log(this.props.childName)
}
}
In order to allow both, proper props definition and the child class derive from the parent class you have to include the props type in your definition:
interface ParentProp<T> {
parentName: string;
}
export class Parent<T, P = ParentProp<T>, S = {}, SS = any> extends React.Component<P, S, SS> {
public printName() {
// console.log(this.props.parentName); Doesn't compile, as P can be any prop interface.
}
}
interface ChildProp<T> {
childName: string;
}
export class Child<T> extends Parent<T, ChildProp<T>> {
public printName() {
console.log(this.props.childName);
}
}

Lit-elements, the idiomatic way to write a controlled component

I'm working with lit-elements via #open-wc and is currently trying to write a nested set of components where the inner component is an input field and some ancestor component has to support some arbitrary rewrite rules like 'numbers are not allowed input'.
What I'm trying to figure out is what the right way to built this is using lit-elements. In React I would use a 'controlled component' see here easily forcing all components to submit to the root component property.
The example below is what I've come up with using Lit-Elements. Is there a better way to do it?
Please note; that the challenge becomes slightly harder since I want to ignore some characters. Without the e.target.value = this.value; at level-5, the input elmement would diverge from the component state on ignored chars. I want the entire chain of components to be correctly in sync, hence the header tags to exemplify.
export class Level1 extends LitElement {
static get properties() {
return {
value: { type: String }
};
}
render() {
return html`
<div>
<h1>${this.value}</h1>
<level-2 value=${this.value} #input-changed=${this.onInput}></level-2>
</div>`;
}
onInput(e) {
this.value = e.detail.value.replace(/\d/g, '');
}
}
...
export class Level4 extends LitElement {
static get properties() {
return {
value: { type: String }
};
}
render() {
return html`
<div>
<h4>${this.value}</h4>
<level-5 value=${this.value}></level-5>
</div>`;
}
}
export class Level5 extends LitElement {
static get properties() {
return {
value: { type: String }
};
}
render() {
return html`
<div>
<h5>${this.value}</h5>
<input .value=${this.value} #input=${this.onInput}></input>
</div>`;
}
onInput(e) {
let event = new CustomEvent('input-changed', {
detail: { value: e.target.value },
bubbles: true,
composed: true
});
e.target.value = this.value;
this.dispatchEvent(event);
}
}
export class AppShell extends LitElement {
constructor() {
super();
this.value = 'initial value';
}
render() {
return html`
<level-1 value=${this.value}></level-1>
`;
}
}
Added later
An alternative approach was using the path array in the event to access the input element directly from the root component.
I think it's a worse solution because it results in a stronger coupling accross the components, i.e. by assuming the child component is an input element with a value property.
onInput(e) {
const target = e.path[0]; // origin input element
this.value = e.path[0].value.replace(/\d/g, '');
// controlling the child elements value to adhere to the colletive state
target.value = this.value;
}
Don't compose your events, handle them in the big parent with your logic there. Have the children send all needed info in the event, try not to rely on target in the parent's event handler.
To receive updates, have your components subscribe in a shared mixin, a la #mishu's suggestion, which uses some state container (here, I present some imaginary state solution)
import { subscribe } from 'some-state-solution';
export const FormMixin = superclass => class extends superclass {
static get properties() { return { value: { type: String }; } }
connectedCallback() {
super.connectedCallback();
subscribe(this);
}
}
Then any component-specific side effects you can handle in updated or the event handler (UI only - do logic in the parent or in the state container)
import { publish } from 'some-state-solution';
class Level1 extends LitElement {
// ...
onInput({ detail: { value } }) {
publish('value', value.replace(/\d/g, ''));
}
}

Can a lit-element track a variable outside its scope

In my lit-element I'm rendering something based on an outside variable. How can I know to update when that variable changes?
import { LitElement, html } from 'lit-element';
import './element01.js';
class Layout extends LitElement {
createRenderRoot(){ return this; }
static get properties() {
return {
settings: { Object }
};
}
constructor() {
super();
}
render() {
return html`
${(settings.foo === 'bar')? html`<my-element01 />` : null}
`;
}
}
customElements.define('my-layout', Layout);
The settings object is being modified outside, how can this element know to update? I'm not using any other framework.
The index.html file should always have a
<my-app></my-app>
Within the my-app element, you will be able to use all the features of litElement such as
class MyApp extends LitElement {
static get properties() {
return {
prop: {type: Object},
};
}
render() {
return html`
<my-element .prop="${this.prop}"</my-element>
<my-server .prop="${this.prop}"</my-server>
<button #onClick='${(e) => this.prop += 1}' >change the settings value</button>
`;
}
}
Here one example I tried to illustrate. settings property changed outside of litElement and effected in litElement.
demo
index.html
...
<my-layout></my-layout>
<br>
<button onClick="_buttonClicked()" >change the settings value</button>
<script>
document.querySelector('my-layout').settings={foo:"bar"};
function _buttonClicked (e) {
document.querySelector('my-layout').settings = {foo:"baz"};
}
</script>
my-layout :
import { LitElement, html } from 'lit-element';
//import './element01.js';
class Layout extends LitElement {
createRenderRoot(){ return this; }
static get properties() {
return {
settings: { Object }
};
}
constructor() {
super();
}
render() {
return html`
${this.settings.foo === 'bar'? html`<span> element01 will be rendered</span>` : null}
`;
}
}
customElements.define('my-layout', Layout)
I made some syntax corrections

Categories