Angular 指南

学习如何用 @reelkit/angular.

触摸优先
带惯性和吸附的滑动
键盘导航
方向键 + Escape
滚轮滚动
可选,带防抖
虚拟化
10,000+ 条目,DOM 里只有 3 个
指示器
Instagram 风格的圆点滚动
编程式 API
通过 apiReady 调用 next()、prev()、goTo()
循环模式
无限循环导航
方向可选
竖向或横向
基于信号
OnPush 搭配 Angular 信号

rk-reel 组件

rk-reel 组件包装了核心的滑动控制器。它是独立组件,使用 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);
  }
}

自动尺寸

size 输入是可选的。省略时, rk-reel 会通过 ResizeObserver 自动测量容器,并适配由 CSS 决定的布局。容器的尺寸必须由父级决定(例如 flex、grid 或显式的 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>

rkReelItem 模板模式

Angular 不用 React 的 render prop,而是在 rkReelItem 上使用结构指令 ng-template。它让虚拟化成为可能 —— 只有可见的幻灯片会被实例化。模板上下文提供三个变量:

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>

内置的导航方式:

  • 触摸 / 滑动: 拖动即可翻页,带惯性和吸附
  • 键盘: 方向键和 Escape
  • 鼠标滚轮: 用 [enableWheel]="true"
  • 编程式: Use the (apiReady) 输出来获取 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;
}

URL 状态

createOverlayUrlState 为浮层构建一个 URL 状态控制器并整个返回,你再把它作为 *UrlOverlay 组件的 [controller] 输入。请在注入上下文中调用它 —— 例如字段初始化器;它会立即挂载,并通过 DestroyRef释放。打开状态归 URL 所有,因此绑定后的浮层会自己打开,链接就是通常的打开方式。参数原本不存在时第一次写入压入一条历史记录,之后每次写入都是替换,所以翻页永远不会把返回键埋掉。留着控制器就能读取 value/position ,也能编程式地驱动它: set(position) 打开, set(null) 关闭,而 set 正是浮层内部在切换幻灯片时使用的底层写入。

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

带路由的应用 —— 请传入基于 Router 的适配器,否则 Router 自己的 location 会过期,下一次导航就会把参数丢掉:

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

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

选项对象接受 param, codec、 locator (这三个都是必填的),外加可选的 adapter. codec 和 locator 是共用同一个 Id,因此对于普通的 ?photo=3 画廊,展开 ...urlIndexKey(() => images().length)即可,它会一次性返回两半。 urlIndexKey 会以 getter 返回的实时数量为上界约束索引,因此过期的 ?photo=99 会被拒绝并自动从 URL 中消失,而不是打开一张从未指定的幻灯片。分页信息流或按身份寻址的画廊则自行提供配套的 codec + locator 。完整的选项表见 Angular API 参考.

ReelIndicator

可选组件,显示 Instagram 风格的进度指示器,标出当前在滑动器中的位置。放在 rk-reel内部时,它会通过上下文自动连接到父级的 count 和 active 值,走的是 RK_REEL_CONTEXT 注入令牌 —— 不需要手动接线。

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" />

apiReady 输出 —— 基于信号的模式

(apiReady) 输出会在组件挂载并测量完成后触发一次。它发出一个 ReelApi 对象,你可以存下来做命令式导航。用 Angular 信号保存这个引用,与 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);
}

要点

  • 独立组件

    把 ReelComponent, RkReelItemDirective, and optionally ReelIndicatorComponent 直接放进组件的 imports 数组

  • ng-template + rkReelItem

    Angular 里对应 React itemBuilder 属性的写法 —— 实现虚拟化

  • apiReady

    只触发一次的输出,带上命令式导航 API —— 不需要 ViewChild 查询

  • afterChange

    发出 { index, indexInRange } —— 跟踪当前索引以更新界面

  • 默认 OnPush

    所有组件都使用 ChangeDetectionStrategy.OnPush 和 Angular 信号以获得最佳性能

性能建议

  • 让幻灯片模板保持轻量

    rkReelItem 模板会为每张可见幻灯片执行(通常同时 3 张)。不要在里面做重计算或写太深的嵌套结构。

  • 临近边缘时加载数据

    使用 (afterChange) 来检测用户是否接近末尾,并在幻灯片用完之前取下一批 —— 这样就能做无限滚动的信息流。

  • 命令式状态用信号

    把 ReelApi 引用和当前索引存在 Angular 信号里,既能细粒度响应,又不会触发整个组件重渲染。

  • 在可滚动页面里关掉滚轮

    把 [enableWheel]="false" ,当滑动器嵌在可滚动布局里时,避免抢走页面滚动。

下一步