Guia para Angular
Aprenda a montar sliders com @reelkit/angular.
Componente rk-reel
O componente rk-reel embrulha o controlador do slider do core. É standalone e usa ChangeDetectionStrategy.OnPush.
import { Component } from '@angular/core';
import { ReelComponent, ReelIndicatorComponent, RkReelItemDirective } from '@reelkit/angular';
@Component({
selector: 'app-root',
standalone: true,
imports: [ReelComponent, ReelIndicatorComponent, RkReelItemDirective],
template: `
<rk-reel
[count]="items.length"
style="width: 100%; height: 100dvh"
direction="vertical"
[enableWheel]="true"
(afterChange)="onAfterChange($event)"
>
<ng-template rkReelItem let-i let-size="size">
<div [style.width.px]="size[0]" [style.height.px]="size[1]"
[style.background]="items[i].color"
style="display:flex;flex-direction:column;align-items:center;justify-content:center;color:#fff">
<div style="font-size:1.5rem;font-weight:bold">{{ items[i].title }}</div>
<div style="font-size:0.875rem;opacity:0.8">{{ items[i].subtitle }}</div>
</div>
</ng-template>
<div style="position:absolute;right:12px;top:50%;transform:translateY(-50%);z-index:10">
<rk-reel-indicator direction="vertical" />
</div>
</rk-reel>
`,
})
export class AppComponent {
items = [
{ title: 'Virtualized', subtitle: 'Only 3 slides in DOM', color: '#6366f1' },
{ title: 'Touch First', subtitle: 'Native swipe gestures', color: '#8b5cf6' },
{ title: 'Zero Deps', subtitle: 'Tiny bundle size', color: '#7c3aed' },
{ title: 'Keyboard Nav', subtitle: 'Full a11y support', color: '#ec4899' },
{ title: 'SSR Ready', subtitle: 'Works everywhere', color: '#14b8a6' },
{ title: '60fps', subtitle: 'Smooth animations', color: '#f59e0b' },
];
onAfterChange(event: { index: number; indexInRange: number }) {
console.log('Current index:', event.index);
}
}Dimensionamento automático
O input size é opcional. Sem ele, o rk-reel mede o próprio contêiner com ResizeObserver e acompanha o layout definido no CSS. O contêiner precisa ganhar tamanho do elemento pai (flex, grid ou dimensões explícitas no CSS).
<!-- Explicit size (fixed) -->
<rk-reel [count]="items.length" [size]="[400, 600]">
<ng-template rkReelItem let-i let-size="size"> ... </ng-template>
</rk-reel>
<!-- Auto-size (responsive — sized by CSS) -->
<rk-reel [count]="items.length" style="width: 100%; height: 100dvh">
<ng-template rkReelItem let-i let-size="size"> ... </ng-template>
</rk-reel>Padrão de template rkReelItem
No lugar da render prop do React, o Angular usa a diretiva estrutural rkReelItem em um ng-template. É ela que viabiliza a virtualização — só os slides visíveis são instanciados. O contexto do template entrega três variáveis:
<ng-template rkReelItem let-i let-indexInRange="indexInRange" let-size="size">
<!--
$implicit (let-i) : number — absolute slide index (0 to count-1)
indexInRange : number — position in visible window (0, 1, or 2)
size : [number, number] — [width, height] of the container
-->
<app-slide [data]="items[i]"
[style.width.px]="size[0]"
[style.height.px]="size[1]" />
</ng-template>Navegação
Formas de navegação já incluídas:
- Toque/Deslizar: arraste para navegar, com inércia e encaixe
- Teclado: setas e Escape
- Roda do mouse: ligue com
[enableWheel]="true" - Programática: use o output
(apiReady)para obternext(),prev(),goTo()
import { Component } from '@angular/core';
import { ReelComponent, RkReelItemDirective, type ReelApi } from '@reelkit/angular';
@Component({
selector: 'app-root',
standalone: true,
imports: [ReelComponent, RkReelItemDirective],
template: `
<rk-reel
[count]="10"
[size]="[400, 600]"
(apiReady)="api = $event"
>
<ng-template rkReelItem let-i let-size="size">
<app-slide [index]="i" [size]="size" />
</ng-template>
</rk-reel>
<button (click)="api?.prev()">Prev</button>
<button (click)="api?.next()">Next</button>
<button (click)="api?.goTo(5)">Go to 5</button>
`,
})
export class AppComponent {
api: ReelApi | undefined;
}Estado na URL
createOverlayUrlState monta um controlador de estado na URL para o overlay e devolve o controlador inteiro; você o entrega a um componente *UrlOverlay pelo input [controller]. Chame-o em um contexto de injeção — na inicialização de um campo; ele se liga na hora e se solta pelo DestroyRef. Quem manda no estado aberto é a URL, então um overlay ligado a ela se abre sozinho e o link vira a forma normal de abrir. A primeira escrita de um parâmetro ausente empilha uma entrada no histórico e cada escrita seguinte a substitui, então percorrer os itens nunca soterra o botão voltar. Guarde o controlador para ler value/position e para conduzi-lo por código: set(position) abre, set(null) fecha, e set é exatamente a mesma escrita de baixo nível que o overlay usa internamente ao trocar de slide.
import { Component, signal } from '@angular/core';
import { RouterLink } from '@angular/router';
import { RkLightboxUrlOverlayComponent } from '@reelkit/angular-lightbox';
import { createOverlayUrlState, urlIndexKey } from '@reelkit/angular';
@Component({
imports: [RkLightboxUrlOverlayComponent, RouterLink],
template: `
@for (image of images(); track image.src; let i = $index) {
<a [routerLink]="[]" [queryParams]="{ photo: i }">
<img [src]="image.src" alt="" />
</a>
}
<rk-lightbox-url-overlay [controller]="photo" [items]="images()" />
`,
})
export class GalleryComponent {
protected readonly images = signal(photos);
// Attaches now, releases on destroy.
protected readonly photo = createOverlayUrlState({
param: 'photo',
...urlIndexKey(() => this.images().length),
});
}App com rotas — passe um adaptador ligado ao Router, senão a localização dele fica desatualizada e a navegação seguinte derruba o parâmetro:
import { createRouterUrlAdapter } from '@reelkit/angular/ng-router-url-adapter';
protected readonly photo = createOverlayUrlState({
param: 'photo',
adapter: createRouterUrlAdapter(),
...urlIndexKey(() => this.images().length),
});O objeto de opções recebe param, codec e locator (os três obrigatórios), mais um adapter opcional. codec e locator formam um par combinado que divide o mesmo Id, então uma galeria simples com ?photo=3 espalha ...urlIndexKey(() => images().length), que devolve as duas metades de uma vez. urlIndexKey limita o índice pela contagem atual que o getter devolve, então um ?photo=99 velho é rejeitado e se cura sozinho saindo da URL, em vez de abrir um slide que ninguém nomeou. Um feed paginado ou uma galeria endereçada por identidade fornece o próprio par codec + locator. A tabela completa de opções está na referência da API para Angular.
ReelIndicator
Componente opcional que mostra indicadores de progresso no estilo Instagram, sinalizando a posição atual dentro do slider. Colocado dentro de um rk-reel, ele se liga sozinho aos valores de count e active do componente pai pelo token de injeção RK_REEL_CONTEXT — sem ligação manual.
<!-- Auto-connect: count and active are inherited from parent rk-reel -->
<rk-reel [count]="10" [size]="[400, 600]">
<ng-template rkReelItem let-i let-size="size"> ... </ng-template>
<rk-reel-indicator direction="vertical" />
</rk-reel>
<!-- Manual usage: pass count and active explicitly (e.g. outside a rk-reel) -->
<rk-reel-indicator [count]="10" [active]="currentIndex" />Output apiReady — padrão baseado em sinais
O output (apiReady) dispara uma única vez, depois de o componente ser montado e medido. Ele emite um objeto ReelApi que você guarda e usa para navegar de forma imperativa. Guardar essa referência em um sinal do Angular combina bem com a detecção de mudanças OnPush.
import { Component, signal } from '@angular/core';
import { ReelComponent, RkReelItemDirective, type ReelApi } from '@reelkit/angular';
@Component({
selector: 'app-root',
standalone: true,
imports: [ReelComponent, RkReelItemDirective],
template: `
<rk-reel
[count]="items.length"
style="width: 100%; height: 100dvh"
direction="vertical"
[enableWheel]="true"
(apiReady)="reelApi.set($event)"
(afterChange)="currentIndex.set($event.index)"
>
<ng-template rkReelItem let-i let-size="size">
<div [style.width.px]="size[0]" [style.height.px]="size[1]">
{{ items[i].title }}
</div>
</ng-template>
</rk-reel>
<div style="position:absolute;bottom:16px;left:50%;transform:translateX(-50%)">
<button (click)="reelApi()?.prev()"
[disabled]="currentIndex() === 0">Prev</button>
<button (click)="reelApi()?.next()"
[disabled]="currentIndex() === items.length - 1">Next</button>
</div>
`,
})
export class AppComponent {
readonly items = [
{ title: 'Slide 1', color: '#6366f1' },
{ title: 'Slide 2', color: '#8b5cf6' },
{ title: 'Slide 3', color: '#ec4899' },
];
readonly reelApi = signal<ReelApi | undefined>(undefined);
readonly currentIndex = signal(0);
}Pontos principais
- Componente standalone
Importe
ReelComponent,RkReelItemDirectivee, se quiser,ReelIndicatorComponentdireto no arrayimportsdo seu componente - ng-template + rkReelItem
O equivalente, no Angular, à prop
itemBuilderdo React — é o que viabiliza a virtualização - apiReady
Output que dispara uma vez com a API imperativa de navegação — sem precisar consultar ViewChild
- afterChange
Emite
{ index, indexInRange }— acompanhe o índice atual para atualizar a interface - OnPush por padrão
Todos os componentes usam
ChangeDetectionStrategy.OnPushe sinais do Angular, para tirar o máximo de desempenho
Dicas de desempenho
- Deixe os templates dos slides leves
O template
rkReelItemroda para cada slide visível (normalmente 3 por vez). Evite cálculos pesados ou estruturas muito aninhadas lá dentro. - Carregue os dados perto da borda
Use
(afterChange)para perceber quando o leitor se aproxima do fim e busque o lote seguinte antes que os slides acabem — é assim que nasce um feed sem fim. - Use sinais para o estado imperativo
Guarde a referência de
ReelApie o índice atual em sinais do Angular, para ter reatividade fina sem provocar a re-renderização do componente inteiro. - Desligue a roda em páginas roláveis
Defina
[enableWheel]="false"quando o slider estiver dentro de um layout rolável, para não capturar a rolagem da página.
Próximos passos
- Referência da API para Angular - todos os inputs, outputs e métodos disponíveis
- Reel Player - player de vídeo no estilo TikTok/Reels
- Lightbox - galeria de imagens e vídeos
- Stories Player - visualizador de stories no estilo Instagram