Guía de Angular

Aprende a crear sliders con @reelkit/angular.

Táctil ante todo
Deslizamiento con inercia y ajuste
Teclado
Flechas + Escape
Rueda del ratón
Opcional, con debounce
Virtualizado
Más de 10.000 elementos, 3 en el DOM
Indicadores
Puntos que se desplazan al estilo de Instagram
API por código
next(), prev(), goTo() mediante apiReady
Modo bucle
Navegación circular infinita
Dirección
Vertical u horizontal
Basado en señales
OnPush con señales de Angular

Componente rk-reel

El componente rk-reel envuelve el controlador del slider del core. Es standalone y usa ChangeDetectionStrategy.OnPush.

app.component.ts
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);
  }
}

Tamaño automático

El input size es opcional. Sin él, rk-reel mide su contenedor con ResizeObserver y se adapta al diseño que marque el CSS. El tamaño del contenedor lo tiene que dar su padre (por ejemplo, flex, grid o dimensiones explícitas en CSS).

html
<!-- 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>

Patrón de plantilla rkReelItem

En lugar de la render prop de React, Angular usa la directiva estructural rkReelItem sobre un ng-template. Esto hace posible la virtualización: solo se instancian los slides visibles. El contexto de la plantilla ofrece tres variables:

html
<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>

Métodos de navegación incluidos:

  • Táctil / deslizar: arrastra para navegar con inercia y ajuste al slide
  • Teclado: teclas de flecha y Escape
  • Rueda del ratón: se activa con [enableWheel]="true"
  • Por código: usa el output (apiReady) para obtener next(), prev(), goTo()
typescript
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 en la URL

createOverlayUrlState crea un controlador de estado en la URL para un overlay y lo devuelve entero; después se lo pasas a un componente *UrlOverlay en su input [controller]. Llámalo en un contexto de inyección, como el inicializador de un campo; se conecta al momento y se libera con DestroyRef. La URL es dueña del estado abierto, así que un overlay vinculado se abre solo y lo habitual es abrirlo con un enlace. La primera escritura de un parámetro ausente añade una entrada al historial y las siguientes la sustituyen, así que pasar slides nunca entierra el botón de volver. Guarda el controlador para leer value/position y manejarlo por código: set(position) abre, set(null) cierra, y set es la misma escritura de bajo nivel que usa el overlay internamente al cambiar de slide.

typescript
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),
  });
}

En una aplicación con router, pasa un adaptador basado en el Router; si no, la ubicación del propio Router queda desfasada y su siguiente navegación pierde el parámetro:

typescript
import { createRouterUrlAdapter } from '@reelkit/angular/ng-router-url-adapter';

protected readonly photo = createOverlayUrlState({
  param: 'photo',
  adapter: createRouterUrlAdapter(),
  ...urlIndexKey(() => this.images().length),
});

El objeto de opciones recibe param, codec y locator (los tres obligatorios), más un adapter opcional. codec y locator son un par a juego que comparte el mismo Id, así que una galería simple con ?photo=3 usa ...urlIndexKey(() => images().length), que devuelve las dos mitades a la vez. urlIndexKey limita el índice con el recuento actual que devuelve el getter, así que un ?photo=99 desfasado se rechaza y se limpia solo de la URL en lugar de abrir un slide que nunca se nombró. Un feed paginado o una galería por identidad aporta su propio par de codec + locator. La tabla completa de opciones está en la referencia de la API de Angular.

ReelIndicator

Componente opcional que muestra indicadores de progreso al estilo de Instagram con la posición actual en el slider. Colocado dentro de un rk-reel, se conecta solo a los valores count y active del padre a través del token de inyección RK_REEL_CONTEXT, sin conectar nada a mano.

html
<!-- 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 — patrón basado en señales

El output (apiReady) se lanza una vez, después de que el componente se monte y se mida. Emite un objeto ReelApi que puedes guardar y usar para navegar de forma imperativa. Guardar esta referencia en señales de Angular encaja bien con la detección de cambios OnPush.

typescript
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);
}

Puntos clave

  • Componente standalone

    Importa ReelComponent, RkReelItemDirective y, si lo necesitas, ReelIndicatorComponent directamente en el array imports de tu componente

  • ng-template + rkReelItem

    El equivalente en Angular de la prop itemBuilder de React: hace posible la virtualización

  • apiReady

    Output que se lanza una vez con la API de navegación imperativa, sin consultas con ViewChild

  • afterChange

    Emite { index, indexInRange }: sigue el índice actual para actualizar la interfaz

  • OnPush por defecto

    Todos los componentes usan ChangeDetectionStrategy.OnPush y señales de Angular para el máximo rendimiento

Consejos de rendimiento

  • Mantén ligeras las plantillas de los slides

    La plantilla rkReelItem se ejecuta para cada slide visible (normalmente 3 a la vez). Evita dentro cálculos pesados o estructuras muy anidadas.

  • Carga datos cerca del final

    Usa (afterChange) para detectar cuándo el usuario se acerca al final y pide el siguiente lote antes de que se acaben los slides; así puedes tener feeds con scroll infinito.

  • Usa señales para el estado imperativo

    Guarda la referencia a ReelApi y el índice actual en señales de Angular para tener una reactividad precisa sin re-renderizar todo el componente.

  • Desactiva la rueda en páginas con scroll

    Pon [enableWheel]="false" cuando el slider esté dentro de un diseño con scroll para no capturar el desplazamiento de la página.

Siguientes pasos