Load listener for custom elements? How to?

I have created a backend function which returns an array. In the front-end I call this function and pass array (with JSON stringify) to the custom element using setAttribute API.

In the custom element I parse incoming array and create custom components with it. Problem is how could I await incoming attribute because custom element is not waiting it just checking attribute and gets null. And it’s not working just because of this I added timeout and tested what would happen it would wait and it’s working when it’s able to get data from front-end.

How can I make async await system between frontend and custom element? Here is the codes:

Front-end:

import { stateChanger } from 'public/Utilities/Utilities.js'
import { getAvailableProducts } from 'backend/Support/Custom-Setup-System/data.jsw'

$w.onReady(function () {
    getAvailableProducts()
        .then((results) => {
            console.log(results)
            $w("#productSelectorElement").setAttribute("data", JSON.stringify(results))
            $w("#productSelectorElement").on('onClick', event => {
                console.log(event.detail)
            });
        })
});

Custom Element:

const createStyle = () => {
    let style = document.createElement('style');
    style.innerHTML = `
    p {
        color: white;
        font-family: Poppins;
        font-size: 16px;
        padding: 0;
        margin: 0;
        cursor: pointer;
    }

    h4 {
        color: white;
        font-family: Poppins;
        font-weight: 500;
        font-size: 20px;
        margin: 0;
        padding: 0;
    }

    .grid {
        width: var(--customElementWidth);
        display: grid;
        grid-template-columns: repeat(5, minmax(150px, auto));
        grid-auto-rows: minmax(170px, auto);
        justify-content: center;
        align-items: center;
        grid-gap: 20px;
    }

    .container {
        --state: none;
        --opacity: 0;
        width: 150px;
        height: 170px;
        display: grid;
        justify-content: center;
        align-items: center;
        grid-template-columns: repeat(2, 1fr);
        border: none;
        border-radius: 15px;
        background-color: white;
        transition: 0.3s ease-in-out;
        cursor: pointer;
    }

    .content {
        height: 130px;
        display: var(--state);
        grid-template-rows: 1fr 1fr;
        grid-gap: 5px;
        margin-left: -50px;
        margin-right: 20px;
        justify-content: start;
        align-items: center;
        animation: opacity 0.4s ease-in;
    }

    .image {
        display: grid;
        justify-content: start;
        align-items: center;
    }

    .image img {
        object-fit: cover;
        height: 130px;
        width: 110px;
        border-radius: 15px;
        margin-left: 20px;
    }

    .container:hover {
        background-color: green;
        width: 400px;
        --state: grid;
        cursor: pointer;
    }

    .title {
        align-self: end;
    }

    .text {
        margin-top: 4px;
        align-self: start;
    }

    @keyframes opacity {
        from {
            opacity: 0;
            display: none;
        }

        to {
            opacity: 1;
            display: grid;
        }
    }
    `
    return style;
}

let innerHtml = (idIdentity) => {
    let html = `
            <div class="image">
                <img id="image${idIdentity}" src="./normal-gölgesiz.png" alt="">
            </div>
            <div class="content">
                <h4 class="title" id="title${idIdentity}">Yayıncı Paketi</h4>
                <p class="text" id="text${idIdentity}">Seçmek İçin Tıklayın</p>
            </div>
        `
    return html;
}

const createGrid = () => {
    const grid = document.createElement('div')
    grid.className = "grid";
    return grid;
}

const createProductTab = (item, idIdentity) => {
    const productTab = document.createElement('div');
    productTab.className = "container";
    productTab.id = `container${idIdentity}`;
    productTab.innerHTML = innerHtml(idIdentity);

    let hoverColor;

    if (item.isSelectable === true) {
        hoverColor = "#008765"
    } else {
        hoverColor = "#E03131"
    }

    productTab.addEventListener('mouseover', () => {
        productTab.style = `background-color: ${hoverColor}`
    })

    productTab.addEventListener('mouseout', () => {
        productTab.style = "background-color: white"
    })

    productTab.querySelector(`#image${idIdentity}`).src = item.productImage;
    productTab.querySelector(`#title${idIdentity}`).innerText = item.productTitle;
    productTab.querySelector(`#text${idIdentity}`).innerText = item.reasonText;

    return productTab;
}

class ProductTab extends HTMLElement {
    constructor() {
        super();
        this.showInfo = true;
        this.attachShadow({ mode: 'open' });
        this.shadowRoot.appendChild(createStyle());
        this.shadowRoot.appendChild(createGrid())
    }

    async getData() {
        return await JSON.parse(this.getAttribute("data"))
    }

    async setupProductTab() {
        let main = this;
        let data = await this.getData()

        data.forEach((item, index) => {
            this.shadowRoot.querySelector('.grid').appendChild(createProductTab(item, index))
            this.shadowRoot.querySelector(`#container${index}`).addEventListener('click', event => {
                main.dispatchEvent(new CustomEvent('onClick', { detail: item }));
            })
        })

    }

    async connectedCallback() {
        console.info("Connected")
        this.setupProductTab()
    }

    disconnectedCallback() {
        console.error("Disconnected!")
    }
}

window.customElements.define('product-tab', ProductTab);

I even tried to use while and for loop even like a noob I tried setInterval non of them worked xd.

Any answer?

I found the solution I can use attributeChangedCallback() Like this:

const createStyle = () => {
    let style = document.createElement('style');
    style.innerHTML = `
    p {
        color: white;
        font-family: Poppins;
        font-size: 16px;
        padding: 0;
        margin: 0;
        cursor: pointer;
    }

    h4 {
        color: white;
        font-family: Poppins;
        font-weight: 500;
        font-size: 20px;
        margin: 0;
        padding: 0;
    }

    .grid {
        width: var(--customElementWidth);
        display: grid;
        grid-template-columns: repeat(5, minmax(150px, auto));
        grid-auto-rows: minmax(170px, auto);
        justify-content: center;
        align-items: center;
        grid-gap: 20px;
    }

    .container {
        --state: none;
        --opacity: 0;
        width: 150px;
        height: 170px;
        display: grid;
        justify-content: center;
        align-items: center;
        grid-template-columns: repeat(2, 1fr);
        border: none;
        border-radius: 15px;
        background-color: white;
        transition: 0.3s ease-in-out;
        cursor: pointer;
    }

    .content {
        height: 130px;
        display: var(--state);
        grid-template-rows: 1fr 1fr;
        grid-gap: 5px;
        margin-left: -50px;
        margin-right: 20px;
        justify-content: start;
        align-items: center;
        animation: opacity 0.4s ease-in;
    }

    .image {
        display: grid;
        justify-content: start;
        align-items: center;
    }

    .image img {
        object-fit: cover;
        height: 130px;
        width: 110px;
        border-radius: 15px;
        margin-left: 20px;
    }

    .container:hover {
        background-color: green;
        width: 400px;
        --state: grid;
        cursor: pointer;
    }

    .title {
        align-self: end;
    }

    .text {
        margin-top: 4px;
        align-self: start;
    }

    @keyframes opacity {
        from {
            opacity: 0;
            display: none;
        }

        to {
            opacity: 1;
            display: grid;
        }
    }
    `
    return style;
}

let innerHtml = (idIdentity) => {
    let html = `
            <div class="image">
                <img id="image${idIdentity}" src="./normal-gölgesiz.png" alt="">
            </div>
            <div class="content">
                <h4 class="title" id="title${idIdentity}">Yayıncı Paketi</h4>
                <p class="text" id="text${idIdentity}">Seçmek İçin Tıklayın</p>
            </div>
        `
    return html;
}

const createGrid = () => {
    const grid = document.createElement('div')
    grid.className = "grid";
    return grid;
}

const createProductTab = (item, idIdentity) => {
    const productTab = document.createElement('div');
    productTab.className = "container";
    productTab.id = `container${idIdentity}`;
    productTab.innerHTML = innerHtml(idIdentity);

    let hoverColor;

    if (item.isSelectable === true) {
        hoverColor = "#008765"
    } else {
        hoverColor = "#E03131"
    }

    productTab.addEventListener('mouseover', () => {
        productTab.style = `background-color: ${hoverColor}`
    })

    productTab.addEventListener('mouseout', () => {
        productTab.style = "background-color: white"
    })

    productTab.querySelector(`#image${idIdentity}`).src = item.productImage;
    productTab.querySelector(`#title${idIdentity}`).innerText = item.productTitle;
    productTab.querySelector(`#text${idIdentity}`).innerText = item.reasonText;

    return productTab;
}

class ProductTab extends HTMLElement {
    constructor() {
        super();
        this.showInfo = true;
        this.attachShadow({ mode: 'open' });
        this.shadowRoot.appendChild(createStyle());
        this.shadowRoot.appendChild(createGrid())

    }

    static get observedAttributes() {
        return ['data'];
    }

    attributeChangedCallback(name, oldValue, newValue) {
        let data = JSON.parse(newValue);

        if (name === "data") {
            let main = this;

            data.forEach((item, index) => {
                this.shadowRoot.querySelector('.grid').appendChild(createProductTab(item, index))
                this.shadowRoot.querySelector(`#container${index}`).addEventListener('click', event => {
                    main.dispatchEvent(new CustomEvent('onClick', { detail: item }));
                })
            })
        }
    }

    async connectedCallback() {
        console.info("Connected")
    }

    disconnectedCallback() {
        console.error("Disconnected!")
    }
}

window.customElements.define('product-tab', ProductTab);