user728650
user728650

Reputation: 1986

event listening on child or parent for reacting to changes in siblings

I have created this very common scenario of menu links, in a native vanilla JS web-component format.

<side-nav>
    <nav-item id="1">1</nav-item>
    <nav-item id="2">2</nav-item>
    <nav-item id="3">3</nav-item>
    <nav-item id="4">4</nav-item>
</side-nav>

Naturally, when I click on any of nav-item, I need to

With web-components I have observed that I can handle that in two ways.

Method 1:

jsfiddle

code for <nav-item>

async connectedCallback() {
    this.addEventListener('click' , (event) => {

        this.setAttribute('selected', true);// immediately mark current component selected

        this.dispatchEvent(new CustomEvent('navitem-selected', { bubbles: true, composed: true , detail: { id: this.id} })); // dispatch event, so that parent can loop and deselect other items
    });
}

and code for the parent <side-nav>

async connectedCallback() {

    this.addEventListener('navitem-selected', (event) => {
        let items = this.shadowRoot.querySelector('slot').assignedElements();
        items.forEach((item) => {
            if(item.getAttribute('id') !== event.detail.id) {
                item.removeAttribute('selected');
            }
        });
    });
}

Method 2:

jsfiddle

Completely ignore parent and handle the activation logic only in the <nav-item>

static get observedAttributes() {
    return ['id', 'selected'];
}

attributeChangedCallback(name, oldValue, newValue) {
    if(name == 'id'){
        this.id = newValue;
    }
    if(name == 'selected')
    {
        if(newValue){
            this.shadowRoot.querySelector('.root').classList.add('selected');
        } else {
            this.shadowRoot.querySelector('.root').classList.remove('selected');
        }
    }
}


async connectedCallback() {
    this.addEventListener('click' , (event) => {
        this.dispatchEvent(new CustomEvent('navitem-selected', { bubbles: true, composed: true , detail: { id: this.id} }));
    });
    const hostNode = this.shadowRoot.querySelector('.root').getRootNode().host.parentNode;

    hostNode.addEventListener('navitem-selected', (event) => {
        if(this.id == event.detail.id){
            this.setAttribute('selected', true);
        } else if(this.hasAttribute('selected')){

            this.removeAttribute('selected');
        }
    });
}

Both methods work fine but for the life of me I can not figure out which one is better when considering good programming practices and performance.

I have a general concept that child should not know about siblings or parents, but parents should manage children. I am bit blur on the best practices here and would love to be pulled to light.

Upvotes: 3

Views: 1609

Answers (1)

Intervalia
Intervalia

Reputation: 10975

Think of this like you would if it were a set of standard components. Most standard components do not know anything about any other components. So it is the job of a parent component to listen to the events from the child components and make changes to all of the children, as needed.

The only odd-ball component is the <label> component with the for attribute. You set the for attribute to the id of the component that will receive focus when you click on the <label>. The label that has a click event handler and, if there is a for attribute it looks for a component with that id. If it finds that component then it calls the .focus() function on that component.

Doing this your components are not tied together but can connect together.

If I were writing the code I would do it the first way.

Upvotes: 1

Related Questions