<!DOCTYPE html>
<html>
<head>
    <meta charset="UTF-8">
    <title>Counter HTML Template Web Component Demo</title>

    <!-- Importing Web Components Polyfill -->
    <script src="https://polygit.org/components/webcomponentsjs/webcomponents-loader.js"></script>

    <!-- Import our custom element -->
    <script src="vt-counter.js"></script>
    <script src="vt-counter-html-template.js"></script>

    <style>
        body {
            text-align: center;
        }
    </style>
</head>
<body>
<h1>Counter HTML Template Web Component Demo</h1>
<!-- Using our new custom element -->
<vt-counter-html-template first="10"></vt-counter-html-template>

<!-- For displaying event output -->
<div id="events"></div>

<script>

    // Update the "events" div with the counter has been upgraded by the browser, and also when it fires custom events.

    let events = '';
    const eventElement = document.getElementById('events');

    customElements.whenDefined('vt-counter').then(() => {
        events += '<br>vt-counter upgraded';
        eventElement.innerHTML = events;
    });

    document.addEventListener('vt-counter-started', function(event) {
        events += '<br>Counter Started; value: ' + event.detail.value;
        eventElement.innerHTML = events;
    });

    document.addEventListener('vt-counter-stopped', function(event) {
        events += '<br>Counter Stopped; value: ' + event.detail.value;
        eventElement.innerHTML = events;
    });

</script>
</body>
</html>
Simple example counter using the Custom Elements and HTML Templates.

Repo: https://github.com/kito99/webcomponents-examples

Written by Kito Mann; brought to you by https://virtua.tech.
/**
 vt-counter example custom element (v1 spec). Counts up from `first` (or 0 if `first` is
 not defined). Starts and stops when a user clicks on it.

 @author Kito D. Mann (kito-public at virtua dot com), http://virtua.tech
 */
class VirtuaTrainingCounter extends HTMLElement {

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

    // Store properties as attributes so they work both ways.
    get first() {
        return this.getAttribute('first');
    }

    set first(first) {
        this.value = Number(first);
        this.setAttribute('first', first);
    }

    // Don't store this property as an attribute because it changes frequently.
    get value() {
        return this._value;
    }

    set value(value) {
        const num = Number(value);
        this._value = num;
        this._content.innerText = num;
    }

    constructor() {
        super();
        console.log('inside constructor');
        this._onClick = this._onClick.bind(this);
        this.addEventListener('click', this._onClick);
    }

    /** Fires after an instance has been inserted into the document */
    connectedCallback() {
        console.log('inside connectedCallback');
        this._content = document.createElement('span');
        this._content.innerText = 'Counter';
        this._content.className = 'counter-disabled';
        this.appendChild(this._content);

        this._upgradeProperty('first');
        this.value = this.first || 0;
    }

    /**
     * Fires after an instance has been removed from the document. Here
     * we stop the timer and remove event listeners.
     */
    disconnectedCallback() {
        console.log('inside disconnectedCallback');
        this.stop();
        this.removeEventListener('click', this._onClick);
    };

    /**
     * Fires after an attribute has been added, removed, or updated. Here we
     * change the `value` to `first` and restart the timer if `first` changes.
     */
    attributeChangedCallback(attr, oldVal, newVal) {
        console.log('inside attributeChangedCallback', 'attr:', attr, 'oldVal:', oldVal, 'newVal:', newVal);
        switch (attr) {
            case 'first' :
                // attributeChangedCallback is called before connectedCallback, so this._content
                // may not be defined yet.
                if (this._content) {
                    this.value = newVal;
                    this.stop();
                    this.start();
                }
                break;
        }
    }

    /** Fires after an element has been moved to a new document */
    adoptedCallback() {
        console.log('inside adoptedCallback');
    }

    start() {
        this._content.classList.remove('counter-disabled');
        this._timer = setInterval(() => {
            this.value++;
        }, 2000);
        this.dispatchEvent(new CustomEvent('vt-counter-started', {
            detail: {
                value: this.value,
            },
            bubbles: true
        }));
    }

    stop() {
        clearInterval(this._timer);
        this._timer = null;
        this._content.classList.add('counter-disabled');
        this.dispatchEvent(new CustomEvent('vt-counter-stopped', {
            detail: {
                value: this.value,
            },
            bubbles: true
        }));
    }

    _onClick() {
        if (!this._timer) {
            this.start();
        }
        else{
            this.stop();
        }
    }

    /**
     * If the property was set before the element was upgraded,
     * remove it and set it again so that the proper setter is used.
     */
    _upgradeProperty(prop) {
        if (this.hasOwnProperty(prop)) {
            let value = this[prop];
            delete this[prop];
            this[prop] = value;
        }
    }
}

// Registers <vt-counter> as a custom element
window.customElements.define('vt-counter', VirtuaTrainingCounter);
(function() {

    /**
     * Declare the template here so it can be re-used by multiple instances of the element.
     * Cloning from the template instead of using innerHTML is more performant since the markup
     * is only parsed once.
     */
    const template = document.createElement('template');
    template.innerHTML = `
	
		<style>
		    .counter {
            	color: indigo;
            	font-size: 60pt;
            	font-family: sans-serif;
            	text-align: center;
            	padding: 10px;
            	border: 1px black inset;
        	}
        	
            .counter:hover {
            	cursor: pointer;
        	}

        	.counter-disabled {
           	 	color: gray;
        	}
		</style>
		
		<span id="value" class="counter counter-disabled">Counter</span>
		
		`;

    /**
     vt-counter-html-template example custom element (v1 spec)

     @author Kito D. Mann (kito-public at virtua dot com), http://virtua.tech
     */
    class VirtuaTrainingHtmlTemplateCounter extends VirtuaTrainingCounter {

        /**
         * Fires when an instance was inserted into the document.
         * @override
         */
        connectedCallback() {
            console.log('inside overridden connectedCallback')
            const templateContent = template.content.cloneNode(true);
            this._content = templateContent.getElementById('value');
            this.appendChild(templateContent);

            this._upgradeProperty('first');
            this.value = this.first || 0;
        }
    }


    // Registers <vt-counter-html-template> as a custom element
    window.customElements.define('vt-counter-html-template', VirtuaTrainingHtmlTemplateCounter);
})();