O componente stories é amplamente utilizado por diversos sites e aplicativos. Na minha opinião ele tem algumas semelhanças com o carrossel, que inclusive utilizei bastante no passado em conjunto com a lib jQuery. Neste texto gostaria de compartilhar como podemos construir um componente stories simples, utilizando Angular e um pouco de HTML e CSS. Ao longo do texto também vou abordar alguns assuntos pontuais que podem ser úteis no cotidiano do desenvolvedor.
Estruturando o projeto
Para estruturar o projeto utilizei as seguintes versões:
$ ng version
Angular CLI: 15.2.4
Node: 18.14.2
Package Manager: npm 9.5.0
OS: darwin arm64
Criando o projeto para desenvolver o componente stories:
# Cria o diretório
$ mkdir consolelog-stories
# Acessa o diretório recém criado
$ cd consolelog-stories/
# Cria o projeto Angular
#
# Adicionei o skip-tests e skip-git porque
# é um ambiente de estudo, então não é necessário
# criar testes unitários nem inicializar o
# repositório git
$ ng new projeto-stories --skip-tests --skip-git
? Would you like to add Angular routing? No
? Which stylesheet format would you like to use? CSS
$ cd projeto-stories
# Executa o projeto:
$ npm run start
Executando o projeto na porta padrão (4200):
Agora que o projeto está criado, vamos partir para os arquivos que irão fazer parte do componente stories.
Estruturando o componente
Utilizei o CLI do Angular para criar os arquivos:
$ ng generate module stories
CREATE src/app/stories/stories.module.ts (193 bytes)
$ ng generate component stories --module stories --skip-tests
CREATE src/app/stories/stories.component.css (0 bytes)
CREATE src/app/stories/stories.component.html (22 bytes)
CREATE src/app/stories/stories.component.ts (206 bytes)
UPDATE src/app/stories/stories.module.ts (273 bytes)
$ ng generate class stories/StoriesItem --skip-tests --type model
CREATE src/app/stories/stories-item.model.ts (29 bytes)
Observação: repare que na criação da classe StoriesItem o valor do parâmetro --type é adicionado no final do nome do arquivo: nome-do-arquivo.<valor do type>.ts.
Configurando o prettier (opcional)
Escrevi "opcional" neste tópico porque o procedimento descrito não é obrigatório para o componente que será construído, mas vale investir alguns minutos caso você nunca tenha lido ou usado a lib prettier.
Esta lib, prettier, ajuda a padronizar a formatação do código, é um ótimo recurso para criar um padrão consistente de formatação. Para iniciar sua configuração basta adicioná-la ao projeto utilizando o npm:
npm i prettier --save-dev
O --save-dev instala pacotes que serão usados ao longo de desenvolvimento, mas que não são usados para executar o projeto.
The --save-dev option allows you to save packages under the devDependencies object in your package.json file. Any packages listed under the devDependencies will not be installed when you are in the production environment.
Após a instalação criei o arquivo .prettierrc na raiz do projeto (no mesmo nível do arquivo package.json) para indicar alguns padrões de formatação como quantidade máxima de caracteres em uma linha, tamanho da tabulação, entre outros.
Para saber mais detalhes sobre as opções de configuração consulte este link.
Com isto já podemos utilizar o prettier para formatar um código:
abra o arquivo stories.module.ts que foi gerado pelo CLI do Angular
pressione ctrl + shift + p no Windows ou command + shift + P no MacOS para abrir as opções no Visual Studio Code.
escolha a opção Format Document With...
selecione prettier
Antes da formatação:
Após a formatação:
Aqui vale uma dica, podemos criar um script dentro do package.json para formatar todos os arquivos desejados. Criei o script format para executar esta tarefa:
O legal do uso do prettier é que conseguimos criar uma consistência na formatação do código, assim o projeto inteiro terá a mesma padronização em sua formatação.
Implementação
Voltando ao componente stories, podemos perceber que o stories.component.ts já foi declarado no stories.module.ts, isto porque indicamos o módulo na criação do componente no comando do CLI. Então a única coisa necessária é adicionar o componente no exports para expor o StoriesComponent para quem importar o StoriesModule:
O arquivo stories-item.model.ts terá um modelo de dados para representar o conteúdo que o componente stories irá mostrar. Para isto vamos considerar que cada slide (imagem) que irá aparecer no componente será representada pelo seguinte conteúdo:
descricao: texto que irá aparecer no meio da imagem
descricaoCorHex: cor em hexadecimal do texto (acima)
status: será utilizado no HTML e também na lógica para controlar a transição das imagens
urlImagem: endereço da imagem que será exibida
O mecanismo para gerenciar o conteúdo que é apresentado na tela ficará no stories.component.ts. Acrescentei alguns comentários para facilitar o entendimento:
Para testar a lógica vamos adicionar o seguinte conteúdo no template:
Também não podemos esquecer de importar o StoriesModule no AppModule e ajustar o template do AppComponent:
Executando o projeto podemos observar a transição do slideAtivo e do valor do campo status nos itens:
Até este ponto sabemos que a lógica funciona bem, então podemos focar no HTML e CSS.
Criando o HTML e CSS
Para não deixar o texto tão extenso colei abaixo o código completo do HTML e CSS com vários comentários para facilitar o entendimento:
Resultado:
Aplicando eventos de teclado
Até este ponto o componente já troca as imagens a cada intervalo de tempo definido no componente. A ideia deste tópico é explorar um mecanismo para avançarmos ou retrocedermos a sequência de imagens através do teclado, das teclas direcionais esquerda e direita.
Olha o código podemos observar que já existe um método chamado avancarSlide que mostra a próxima imagem da sequência. Então para retroceder podemos criar um novo método conforme abaixo:
Para ativar um destes métodos quando as setas direita e esquerda do teclado forem pressionadas, podemos utilizar o decorator HostListener passando o parâmetro document:keydown.<tecla>:
Veja no resultado abaixo que ao pressionar as setas do teclado (esquerda e direita) o componente avança ou retrocede a imagem:
Considerações
Existem diversas formas de chegarmos neste mesmo resultado, a que apresentei aqui é apenas uma delas.
Deixo como sugestão, além de estudar o código, construir uma nova função para que a transição das imagens pause enquanto o botão do mouse estiver pressionado em cima da imagem.