<button id="add-counter">Add counter</button>
<button id="increment">Increment</button>
<div class="counters-container"></div>
.counters-container {
  display: flex;
  gap: 1rem;
  flex-wrap: wrap;
}

.counter {
  padding: 4px 8px;
  display: flex;
  align-items: center;
  gap: 1rem;
}

.counter:nth-child(even) {
  background: lightgreen;
}

.counter:nth-child(odd) {
  background: lightpink;
}
class EventBus {
  // List of all subscribed components
  subscribers = new Set();
  
  // Add subscriber
  subscribe(subscriberCallback) {
    this.subscribers.add(subscriberCallback);
    subscriberCallback(reactiveData.counter);
  }
  
  // Remove subscriber
  unsubscribe(subscriberCallback) {
    this.subscribers.delete(subscriberCallback);
  }

  // Letting the subscribers know about new change
  publish() {
    this.subscribers.forEach(subscriberCallback => subscriberCallback(reactiveData.counter));
  }
}

const eventBus = new EventBus();

// Initial DOM update
eventBus.publish();

function renderCounterComponent() {
  // Rendering a counter component that shows the counter number and also a button to remove itself.
  const counter = document.createElement('div');
  counter.classList.add('counter');
  
  const counterText = document.createElement('span');
  counter.appendChild(counterText);
  function updateCounter(count) {
    // Updating the counter text.
    counterText.innerText = count;
  }
  
  const removeCounterBtn = document.createElement('button');
  removeCounterBtn.innerText = '❌';
  removeCounterBtn.addEventListener('click', () => {
    eventBus.unsubscribe(updateCounter);
    counter.remove();
  })
  counter.appendChild(removeCounterBtn);
  
  eventBus.subscribe(updateCounter);
  document.body.querySelector('.counters-container').appendChild(counter);
}

const addCounter = document.getElementById('add-counter');
addCounter.addEventListener('click', renderCounterComponent);

// Creating a simple object that holds a state. Will be used as the target object.
const data = {
  counter: 0
};

// Configure the handler object and how we are going to follow changes on state and update the DOM.
const handler = {
  get(target, property) {
    // Get the value of a specific property
    return target[property];
  },
  set(target, property, value) {
    // Set the value of a specific property
    target[property] = value;

    // Update the DOM
    eventBus.publish();
  }
};

// Creating the Proxy
const reactiveData = new Proxy(data, handler);

// Listen to click event to set the new counter value
const incrementButton = document.getElementById('increment');
if (incrementButton) {
  incrementButton.addEventListener('click', () => {
    reactiveData.counter++;
  });
}

External CSS

This Pen doesn't use any external CSS resources.

External JavaScript

This Pen doesn't use any external JavaScript resources.