connect(): onde o trabalho real de um controller começa
Uma classe de controller do Stimulus existe assim que o arquivo carrega: todo método está definido, toda declaração static foi lida. Isso não quer dizer que o controller já esteja fazendo algo. O connect() é o método que o Stimulus chama quando um elemento com o identificador desse controller aparece no DOM. O controller de gráficos desta aplicação usa esse momento para a única coisa que não podia acontecer antes.
Um controller que só começa no connect()
app/javascript/controllers/ui/chart_controller.js, o connect() e o render():
export default class extends Controller {
static values = { type: { type: String, default: "bar" }, data: Object, options: Object }
static targets = ["canvas"]
connect() {
this.disconnected = false
this.render()
}
async render() {
const { default: Chart } = await import("chart.js/auto")
// The element can be gone by the time the import resolves.
if (this.disconnected || !this.hasCanvasTarget) return
const ctx = this.canvasTarget
this.chart = new Chart(ctx, { type: this.typeValue, data: this.dataValue, options: { ...this.optionsValue } })
}
}
O connect() chama this.render(), que lê this.canvasTarget, o elemento <canvas> a que o controller está ligado. O connect() só roda quando esse elemento já existe, como a abertura disse. Mas o render() é async. Ele importa o Chart.js de forma dinâmica, uma escolha proposital: a biblioteca tem cerca de 180 KB de JavaScript, fixada com preload: false, e só vale a pena numa página que desenha gráficos. Esse import pode demorar o bastante para o elemento se desconectar antes de terminar. A proteção if (this.disconnected || !this.hasCanvasTarget) return existe por um motivo. A garantia do connect() vale para o instante em que ele roda, e não para o que roda depois de um await.
Onde a aplicação guarda a resposta
O this.disconnected = false, no começo do connect(), e o seu espelho no disconnect() (próxima aula) são o jeito do próprio controller responder "ainda estou aqui?" depois de um await. O Stimulus não confere isso por você. O this.hasCanvasTarget é um helper gerado pelo Stimulus (existe um para cada target declarado) que vira false quando o elemento do target some. É a segunda metade da mesma proteção.
O que dispara o connect()
O Stimulus observa o DOM com um MutationObserver. Ele não fica consultando, não roda num timer e não depende do DOMContentLoaded. Quando um elemento com o atributo data-controller="chart" aparece em qualquer ponto do documento, o Stimulus cria o controller e chama o connect() dele. O elemento pode vir de um carregamento de página, da troca de um Turbo Frame ou de um turbo_stream.append (dois cursos atrás). Um controller conectado por uma atualização de Turbo Stream recebe o mesmo connect() que um presente desde o primeiro carregamento. Nesse instante, só importa que o elemento exista.
Por que isso é mais que outro método
O código que lê um target, um value ou qualquer outra coisa do elemento do controller precisa rodar dentro do connect() ou de algo que o connect() chame. Não pode ficar no corpo da classe nem numa instrução no topo do arquivo, porque nada no elemento está garantido antes disso. O chart_controller.js desta aplicação nunca lê this.canvasTarget fora do render(). E o render() só é chamado pelo connect() e, como as próximas aulas mostram, por callbacks de mudança de value que só rodam com um gráfico já existente. Mesmo ali ele confere se o elemento ainda está presente antes de mexer nele. O seu próprio await quer dizer que o tempo passou desde a última confirmação do connect().
Pontos-chave
- O
connect()roda quando o Stimulus detecta no DOM um elemento com o identificadordata-controllerdo controller, e não quando o arquivo carrega ou a classe é definida. - O
chart_controller.jsdesta aplicação chamathis.render()noconnect(), mas orender()éasync. Ele importa o Chart.js de forma dinâmica, e quando o import termina o elemento pode ter sumido. - A proteção
if (this.disconnected || !this.hasCanvasTarget) returnacompanha o estado de conexão do controller através desseawait. A garantia doconnect()vale para o instante em que ele roda, e não para o que roda depois de uma promise aguardada. - O Stimulus encontra os elementos que se conectam com um
MutationObserver, e não com consultas periódicas ou evento de carregamento. Um carregamento de página, a troca de um Turbo Frame e um append de Turbo Stream disparam oconnect()do mesmo jeito.
Continue com a aula 2
disconnect(): o espelho que desfaz connect()
O disconnect() desfaz o que o connect() começou, destruindo uma instância do Chart.js ou limpando o setTimeout do flash.
Com uma conta grátis você também tem os quizzes, seu progresso e suas anotações, e a discussão.