Lidando com Elementos Globais em Aplicações Sever-Side Rendering (SSR) no Angular
Você já tentou adicionar eventos em elementos globais, window e document por exemplo, em uma aplicação de Server-Side Rendering (SSR)? Se sim, você provavelmente se deparou com este erro:
Geralmente esse erro ocorre quando tentamos manipular elementos globais como o window, porém, em aplicações SSR esses objetos não existem até que seja renderizado no client-side. Para isso, o Angular fornece uma forma simples de lidar com isso, a classe Renderer2.
Conhecendo o Renderer2
O Renderer2 é uma classe abstrata que pode ser extendida para implementar renderização customizada de elementos. O que vamos usar é o método ‘listen’ injetando o Renderer2 no componente:
protected readonly renderer = inject(Renderer2);
Exemplo: Criando um Componente
Neste exemplo, nós vamos criar um componente para deslizar a tela automaticamente para o topo da página, com esse simples evento nós usamos o elemento global ‘window’ e ainda criamos um componente legal de para algumas aplicações.
Vamos começar gerando o componente com o CLI do Angular:
ng generate component scroll-to-top
Arquivo: scroll-to-top.component.ts
import {
AfterContentInit,
Component,
ElementRef,
inject,
OnDestroy,
Renderer2,
signal,
viewChild,
} from '@angular/core';
@Component({
...
selector: 'scroll-to-top',
templateUrl: './scroll-to-top.component.html',
styleUrl: './scroll-to-top.component.scss',
...
})
export class ScrollToTopComponent implements AfterContentInit, OnDestroy {
protected readonly scrollButton = viewChild.required('scrollButton', {
read: ElementRef,
});
protected readonly isDisabled = signal<boolean>(true);
protected readonly renderer = inject(Renderer2);
protected unListen: () => void;
constructor() {
this.unListen = this.renderer.listen('window', 'scroll', () => {
if (scrollY > 300) {
this.isDisabled.set(false);
} else {
this.isDisabled.set(true);
}
});
}
ngAfterContentInit(): void {
const scroll = this.scrollButton().nativeElement;
this.unListen = this.renderer.listen(scroll, 'click', () => {
window.scrollTo({ top: 0, behavior: 'smooth' });
});
}
ngOnDestroy(): void {
this.unListen();
}
}
No código acima, nós criamos um evento no elemento ‘window’ (entre aspas para o Angular saber como tratar o elemento) no constructor do componente, se a posição do scroll na tela for maior que 300 nós habilitamos o clique nele. Usamos o hook de ‘afterContentInit’ para criar o evento de deslizar para o topo da tela (scroll to top). Em ambos os eventos criados nós atribuimos ele a uma função ‘unListen’ do tipo void, isso é essencial para remover o evento quando destruimos o componente.
Sem o Renderer2, você teria que implementar a verificação da plataforma que o usuário está com o ‘isPlatformBrowser’. Com o método ‘listen’ do Renderer2 os eventos são criados apenas no client-side, e não no server-side, evitando erros com elementos globais.
Arquivo: scroll-to-top.component.html
<button #scrollButton [disabled]="this.isDisabled()">↑</button>
No template, colocamos apenas um simples botão.
Arquivo: scroll-to-top.component.scss
button {
position: fixed;
bottom: 4svh;
right: 4svw;
}
O estilo fica a seu critério, no exemplo acima, apenas fixei a posição do botão na tela.
Informações Adicionais
Quero deixar claro que não sou especialista no assunto. Se cometi algum erro ou passei informações incorretas, fique à vontade para corrigir. Também estou aberto a sugestões de melhoria ou qualquer feedback que me ajude a melhorar.