Web Components, o que são e como criar (parte 1)
Tags: javascript, webdev, webcomponents, frontend
Published: 2021-02-25 22:23:21

Essa é a primeira parte de uma série de tutoriais sobre Web Components. Nesse tutorial veremos o que são Web Components e como fazer um.

Resumidamente um Web Component é uma tag customizada, podendo ser uma nova tag, um aprimoramento de uma tag já existente ou de outra tag customizada. Nós utilizamos da API `customElements` para criar esses elementos customizados.

## Definindo um novo elemento

A API `customElements` possuí um método `define` que é utilizado para definir um novo elemento. Ele recebe 3 parâmetros, mas por enquanto vamos mexer somente com os 2 primeiros. O primeiro parâmetro é o nome da tag e o segundo parâmetro é uma classe que herde de `HTMLElement`.
```jsx
class MyText extends HTMLElement {}
customElements.define('my-text', MyText)

// Ou usando uma classe anônima
customElements.define('my-text', class extends HTMLElement {})
```

Repare que o nome da tag está todo em minúsculo e possuí um "-", não é por coincidência, existem algumas regras a seguir:
- Deve possuir no mínimo um "-"(traço).
- Além do "-" o único carácter especial aceito é "_"(underscore).
- Possuir somente letras minúsculas.
- Deve iniciar com no mínimo uma letra.
- Todas as tags devem possuir um fechamento (\<my-tag\>\</my-tag\>), pois o HTML só permite tags auto fechadas, como o `meta` ou `img`, para algumas tags específicas.

O motivo dessas regras é para que o analisador do HTML possa distinguir entre elementos customizados e elementos normais. Além disso, garante que não ocorra problemas com futuras  tags que forem adicionados ao HTML.

## Definindo o conteúdo do componente

Para definir o conteúdo HTML de um elemento você pode simplesmente adicionar o valor ao seu atributo `innerHTML`. Lembrando que seu componente possuí acesso a todos os tipos de propriedades gerais dos elementos HTML, como `innerHTML`, `getAttribute`, `children`, entre outros...

Vamos criar um `index.html` com o seguinte conteúdo:

```html
<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <meta http-equiv="X-UA-Compatible" content="IE=edge">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <link rel="shortcut icon" href="data:image/x-icon;" type="image/x-icon">
  <title>Web components</title>
</head>
<body>
  <my-text></my-text>

  <script>
    class MyText extends HTMLElement {
      constructor() {
        super()
        this.innerHTML = '<h2>Meu Texto</h2>'
      }
    }

    customElements.define('my-text', MyText)
  </script>
</body>
</html>
```

Será possível ver escrito `Meu Texto` na página do navegador.

Nesse mesmo código mova o conteúdo da tag **script** para um arquivo separado e vamos continuar por nele.

O conteúdo desse elemento pode ser facilmente estilizado com CSS. No caso do `h2` do **my-text** seria selecionando o elemento de dentro da tag, por exemplo, `my-text h2` ou `my-text > h2`. Depois veremos mais sobre o uso de CSS com Web Components, mas por enquanto, assim funciona.

### Usando Templates

Nós também podemos utilizar das tags **template** para gerar o conteúdo HTML dos componentes. Caso você não conheça sobre as tags de template, basicamente, tudo que for inserido dentro da tag não será interpretado pelo analisador do HTML. Contudo, esse conteúdo pode ser replicado inúmeras vezes usando a propriedade `content`.

```html
<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <meta http-equiv="X-UA-Compatible" content="IE=edge">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <link rel="shortcut icon" href="data:image/x-icon;" type="image/x-icon">
  <title>Web components</title>
</head>
<body>
  <my-text></my-text>

	<template>
    <h2>Meu Texto</h2>
  </template>

  <script src="js/main.js"></script>
</body>
</html>
```

```jsx
const template = document.querySelector('template')

class MyText extends HTMLElement {
  constructor() {
    super()
    this.append(template.content.cloneNode(true))
  }
}

customElements.define('my-text', MyText)
```

Utilizando o método `cloneNode` de `content` é possível copiar todo o conteúdo interno do template.

## Estendendo elementos

Além de criar novos elementos a API **customElements** também nós permite estender elementos previamente criados.

```jsx
class MyTitle extends MyText {
  constructor() {
    super()
  }
}

customElements.define('my-title', MyTitle)
```

Com o código acima podemos estender o elemento **my-text** que criamos anteriormente. Note que ao inserir `<my-title></my-title>` no html veremos o mesmo resultado de **my-text**. O Elemento **my-title** herda tudo que foi inserido em **my-text** e usando de princípios da programação orientada a objetos você pode alterar a forma como o elemento se comporta.

### Estendendo elementos HTML nativos

Além de criar novos elementos a partir de outros, você também pode criar elementos a partir dos elementos já existentes no HTML.

É bem simples de se implementar e pode ser bastante útil dependendo do seu objetivo.

Para se criar um elemento dessa maneira é necessário que o elemento herde da interface DOM correta. Ao criar um elemento novo herdamos da interface `HTMLElement` e dependendo da tag que você deseja herdar a interface será diferente. Como por exemplo, um `button` usa a interface `HTMLButtonElement`, um `p` usa a interface `HTMLParagraphElement`:

```jsx
class MyParagraph extends HTMLParagraphElement {
  constructor() {
    super()
    this.innerHTML = 'Meu parágrafo!!!'
  }
}

customElements.define('my-parag', MyParagraph, { extends: 'p' })
```

```html
<p is="my-parag"></p>
```

Repare em dois detalhes, primeiro que usamos o terceiro parâmetro do `customElements.define` no qual é um objeto que recebe a opção `extends` em que inserimos qual a tag que desejamos estender e segundo que não colocamos `<my-parag></my-parag>` e sim usando o atributo `is` direto na tag **p.**

## Gerando elementos programaticamente

Você pode gerar um elemento customizado usando a classe em que ele foi definido.

```jsx
const myText = new MyText()
```

E você pode conseguir acesso a essa classe através do método `get` do `customElements`. Ele recebe um parâmetro que será o nome do elemento previamente definido.

```jsx
const MyText = customElements.get('my-text')
const myText = new MyText()
```

No caso de elementos que estendem elementos nativos do HTML, eles podem ser criados das mesmas maneiras já mostradas ou usando `document.createElement` e será necessário passar um segundo parâmetro indicando que ele é um elemento customizado.

```jsx
const myParagraph = document.createElement('p', { is: 'my-parag' })
```

---
## Referências
https://developers.google.com/web/fundamentals/web-components/customelements

## Conclusão

Esse é o básico quando se trata da criação de Web Components. Ainda há bastante a se falar sobre então fique atento as próximas partes. Espero que tenha gostado e qualquer dúvida pode deixar um comentário, no mais até logo!!!