12
Web Components, comunicação entre componentes (parte 5)
Essa é a quinta parte da série de tutoriais sobre Web Components, não deixe de ver as outras partes. Neste tutorial vamos ver um pouco mais sobre algumas abordagens de como podemos fazer uma comunicação entre nossos componentes.
Sim, já que todo componente que criamos se trata de um elemento HTML customizado, nós podemos ouvir e disparar eventos como qualquer outro elemento faz, além de adicionar eventos customizados também. Eventos serão a forma mais comum que terá para fazer a comunicação entre os elementos.
Caso você não sabia nós podemos disparar os eventos do HTML de forma programática, sem a necessidade de interações do usuário.
const clickEvent = new Event('click')
document.querySelector('button').dispatchEvent(clickEvent)
Com esse simples código você verá que o evento atrelado ao botão foi disparado sem que houvesse um real click nele.
A classe Event
recebe dois parâmetros, sendo o primeiro o nome do evento e o segundo sendo um objeto de configuração para o evento, em que podemos configurar coisas como bubbles
, cancelable
, composed
. Para saber mais olhe: https://developer.mozilla.org/en-US/docs/Web/API/Event/Event
Utilizando de uma API muito parecida com a de eventos que acabamos de ver, podemos usar a classe CustomEvent
para criar um evento customizado.
const formErrorEvent = new CustomEvent('form-error', {
detail: new Error('Form Error')
})
Como pode ver a API é praticamente a mesma, no caso dos custom events nós podemos passar o atributo detail
em que podemos passar qualquer valor que queremos propagar a outros elementos.
Essa aliás é uma ótima forma para fazer a comunicação entre os elementos.
Um simples exemplo usando um evento customizado:
<!-- HTML -->
<app-root></app-root>
// Javascript
class AppForm extends HTMLElement {
constructor() {
super()
this.attachShadow({ mode: 'open' })
this.shadowRoot.innerHTML = `
<form>
<input placeholder="Name" />
<button>Submit</button>
</form>
`
}
connectedCallback() {
const input = this.shadowRoot.querySelector('input')
const form = this.shadowRoot.querySelector('form')
form.addEventListener('submit', ev => {
ev.preventDefault()
if(!input.value) {
const formErrorEvent = new CustomEvent('form-error', {
detail: new Error('Empty name field')
})
this.dispatchEvent(formErrorEvent)
}
})
}
}
customElements.define('app-form', AppForm)
class AppRoot extends HTMLElement {
constructor() {
super()
this.attachShadow({ mode: 'open' })
this.shadowRoot.innerHTML = '<app-form></app-form>'
}
connectedCallback() {
this.shadowRoot
.querySelector('app-form')
.addEventListener('form-error', ev => {
console.log(ev.detail.message)
})
}
}
customElements.define('app-root', AppRoot)
Eventos são muito úteis quando queremos obter o valor do resultado de uma operação feita por outro elemento ou simplesmente de ser notificador quando algo ocorrer. Porém, existem situações em que queremos simplesmente que o elemento mude seu comportamento ou estado atual, nessas situações construir uma API é a melhor forma de comunicação, pois nós pedimos ao elemento que ele faça algo e ele internamente faz o que for necessário para que aquilo ocorra.
<!-- HTML -->
<app-root></app-root>
// Javascript
class LightSwitch extends HTMLElement {
// Estado do elemento
#isOn = false
constructor() {
super()
this.attachShadow({ mode: 'open' })
this.shadowRoot.innerHTML = `
<style>
div {
width: max-content;
padding: 14px;
border-radius: 6px;
}
.off {
background-color: #ddd;
}
.on {
background-color: #08c;
}
</style>
<div class="off">
<button>Toggle</button>
</div>
`
}
connectedCallback() {
this.shadowRoot
.querySelector('button')
.addEventListener('click', () => {
this.toggle()
})
}
/*
Método público que pode ser usado
para mudar o estado do elemento
*/
toggle() {
this.#isOn = !this.#isOn
const className = this.#isOn ? 'on' : 'off'
this.shadowRoot.querySelector('div').className = className
}
}
customElements.define('light-switch', LightSwitch)
class AppRoot extends HTMLElement {
constructor() {
super()
this.attachShadow({ mode: 'open' })
this.shadowRoot.innerHTML = `
<light-switch></light-switch>
<button>
Toggle from outside
</button>
`
}
connectedCallback() {
const lightSwitch = this.shadowRoot
.querySelector('light-switch')
this.shadowRoot
.querySelector('button')
.addEventListener('click', () => {
// Chamando o método para alterar o estado do elemento
lightSwitch.toggle()
})
}
}
customElements.define('app-root', AppRoot)
Por terceiros, me refiro a outros elementos ou estruturas na qual podemos delegar a parte da comunicação para uma entidade que não é diretamente quem queremos impactar. Esse tipo de abordagem é muito útil quando queremos que algo seja refletido em vários elementos de uma vez e/ou quando não sabemos quais elementos serão afetados. É uma abordagem muito comum para o gerenciamento de estado, seja específico a alguns componentes ou um estado global.
Devo enfatizar que essa é somente uma forma de gerenciar essa parte de estado compartilhado e afins.
O exemplo abaixo é simples, usando um objeto específico para manter o estado de um contador e utilizando de eventos para capturar as mudanças que acontecerem.
<!-- HTML -->
<app-root></app-root>
// Javascript
class CounterStore {
count = 0
#events = {
onCountChange: []
}
increment() {
this.count++
for(const event of this.#events.onCountChange) {
event()
}
}
onCountChange(listener) {
this.#events.onCountChange.push(listener)
}
}
const counterStore = new CounterStore()
class AppRoot extends HTMLElement {
constructor() {
super()
this.attachShadow({ mode: 'open' })
this.shadowRoot.innerHTML = `
<div>Count: ${counterStore.count}</div>
<button>Increment</button>
`
}
connectedCallback() {
this.shadowRoot
.querySelector('button')
.addEventListener('click', () => {
counterStore.increment()
})
counterStore.onCountChange(() => {
this.shadowRoot
.querySelector('div')
.innerText = `Count: ${counterStore.count}`
})
}
}
customElements.define('app-root', AppRoot)
Agora você viu como podemos mexer com a comunicação entre nossos Web Components, lembrando que isso que mostrei são só abordagens e que é sempre bom lembrar que aqui estamos mexendo com Javascript puro, então há espaço para que você crie sua própria maneira de gerenciar isso. Espero muito que tenha gostado e caso tenha alguma dúvida pode deixar nos comentários e até o próximo!!!
12