跳到主要内容

rxdb-angular

Angular 框架集成库,为 Angular 应用提供 RxDB 支持。基于 Angular Signals 实现响应式数据流。

功能特性​

  • Angular Signals 集成: 查询结果以 Signal 暴露,天然融入 Angular 响应式渲染
  • 响应式查询 Hooks: useGet / useFind / useFindByCursor 等,覆盖基础/树/图仓库查询
  • 无限滚动: useInfiniteScroll 返回 signal 资源(与 React/Vue 同名同形),底层类 InfiniteScrollingList 亦可直接使用
  • 变更检测指令: RxDBEntityChangeDirective 在 OnPush 下实时反映实体编辑
  • 依赖注入: 通过 provideRxDB 完全接入 Angular 依赖注入系统
  • 类型安全: 完整的 TypeScript 类型支持

何时使用​

  • 构建 Angular 应用并需要本地优先数据库
  • 需要响应式数据流与 Angular Signals 无缝集成
  • 需要离线优先的 Angular 应用

安装​

npm install @aiao/rxdb @aiao/rxdb-angular
# 或
pnpm add @aiao/rxdb @aiao/rxdb-angular

使用​

注册 RxDB​

provideRxDB 接受 RxDBSource:实例、Promise,或返回二者之一的工厂。三个框架包收的是同一个联合类型。

import { provideRxDB } from '@aiao/rxdb-angular';

export const appConfig: ApplicationConfig = {
providers: [
// 工厂 / 实例 / Promise / 异步工厂都可以
provideRxDB(() => rxdb)
]
};

异步形态用于「后端按运行环境动态选择」这类场景 —— 静态 import 会把桌面分支打进 web bundle:

provideRxDB(async () => {
const { setupDesktop } = await import('./setup-desktop');
return setupDesktop();
});

provideRxDB 自带 app initializer,会在 bootstrap 阶段建好数据库再放行首帧,因此组件里 inject(RxDB) 始终同步可用。初始化器不会 reject —— 创建失败时 bootstrap 照常完成(否则窗口全白,为这种失败准备的诊断界面反而被失败本身挡在门外),原始异常留到读取时抛出。

在初始化器之外(比如 bootstrap 途中)读取时,用哪个入口取决于「没就绪该怎么办」:

import { useRxDB, useRxDBOptional } from '@aiao/rxdb-angular';

const database = useRxDB(); // 等价于 inject(RxDB):未就绪抛错,创建失败原样抛出创建异常
const maybe = useRxDBOptional(); // 无 provider 或未就绪时返回 undefined,用于渲染 loading 态

生命周期所有权:provider 只销毁自己造的东西。 传工厂或 Promise,实例由 provider 等来,注入器销毁时它负责 disconnectAll();传已就绪的实例,它归调用方所有,provider 不碰 —— 否则一个模块级单例会被某个子注入器的销毁顺手断掉,而没有人会去重连。这条规则三端逐字相同。

查询数据​

import { Component } from '@angular/core';
import { useGet, useFind } from '@aiao/rxdb-angular';

@Component({
selector: 'app-todo',
template: `
@if (todo.isLoading()) {
<span>Loading…</span>
} @else {
<span>{{ todo.value()?.title }}</span>
}
`
})
export class TodoComponent {
readonly todo = useGet(Todo, 'todo-1');
readonly todos = useFind(Todo, { where: { combinator: 'and', rules: [] } });
}

无限滚动​

推荐入口 —— 与 React / Vue 侧的 useInfiniteScroll 同名同形,随注入上下文自动释放:

@Component({/* ... */})
export class TodoListComponent {
// 必须在注入上下文中调用(构造器/字段初始化器)
readonly todos = useInfiniteScroll(Todo, { limit: 50 });

next() {
this.todos.loadMore(); // isLoading 为真或 hasMore 为假时是 no-op
}
}

模板里直接读 signal:

@for (todo of todos.value(); track todo.id) {
<li>{{ todo.title }}</li>
} @if (todos.isLoading()) `{ <spinner /> } @if (todos.isEmpty()) { <empty-state /> }`

也可以直接用底层类:

@Component({/* ... */})
export class TodoListComponent {
// 必须在注入上下文中构造:类内部经 inject(DestroyRef) 注册销毁钩子,
// 并在构造器里建立 effect —— 在普通函数或 service 方法里裸 new 会抛 NG0203。
readonly list = new InfiniteScrollingList(inject(RxDB), Todo, { limit: 50 });
}

list.loadMore(); // 加载下一页
list.refresh(); // 丢弃已加载页面,从头刷新(宿主销毁后是 no-op)

类没有公开的 destroy() —— 清理由 DestroyRef 接管,宿主销毁时自动退订全部页查询。 需要在注入上下文之外持有实例时,用 runInInjectionContext(injector, () => new InfiniteScrollingList(...)), 生命周期即绑定到该 injector。

破坏性变更(下一个 major):isLoading / error / hasMore 由 WritableSignal 收窄为只读 Signal。这三个字段是内部状态机的一部分 —— 外部把 isLoading 改成 false 能绕过 loadMore 的并发 guard 发出重复页请求, 把 hasMore 改成 true 能越过终页。状态现在只能经 loadMore / refresh 改变, 与 React 侧「返回纯值」的只读语义一致。若此前依赖 todos.isLoading.set(...) 之类的写法,请改为调用 loadMore() / refresh(),或在组件里自持一个 signal。

OnPush 下的实时变更检测​

实体是原地可变的类实例,引用不变,OnPush 视图看不到它们的字段变化。rxdbChangeDetector 把实体的 patches$ 接上 markForCheck:

<div [rxdbChangeDetector]="entity"></div>
<div [debounceTime]="200" [rxdbChangeDetector]="entity"></div>

debounceTime 与 auditTime 单位是毫秒,同时设置时串联生效(顺序 debounceTime → auditTime), 仅正有限值生效:0、负值、NaN、Infinity 一律表示禁用,两者都禁用时 patch 同步透传。

异步操作​

const save = useAction((todo: Todo) => repository.save(todo));

save.isPending(); // Signal<boolean>
save.execute(todo);

isPending 是并发计数而不是布尔开关:N 次调用同时在途时它一直为真,直到最后一个 settle; 计数在 finally 里回退,因此失败也会正确复位。有意不做去重与取消 —— 重复点击会真的执行多次,错误原样冒泡给调用方。

持久化状态​

usePersistedState 与既有的柯里化 useState 是同一份状态,只是签名扁平:

const theme = usePersistedState('my-app', 'theme', 'dark');
theme.value.set('light'); // 落盘到 'my-app:theme'

// 等价写法
const same = useState('my-app')('theme').signal('dark'); // === theme.value

两者共用同一张 root 注册表、同一套键格式与失败语义。扁平签名的存在是为了三端对齐: React 的 hooks 规则不允许「从返回对象的方法里再调 hook」,柯里化形态在 React 侧无法复现。

  • 必须在 Angular 注入上下文中调用。
  • 后续调用传入的 initialValue 会被忽略,但仍参与类型标签校验 —— 同 key 换值类型直接抛错。
  • namespace 与 name 在键里各自转义,不会互相串号;含 : 或 % 的旧键在首次读取时一次性迁移。
  • 写盘失败不抛错,signal 值照常更新,失败经 persistError 暴露。
  • SSR 下不读也不写 localStorage;暂不监听 storage 事件,因此不跨标签页同步。

三端 API 对照​

同功能同 API 是本仓库的硬约束,框架惯例允许容器形态与命名差异,不允许能力缺失:

能力AngularVueReact
查询useGet / useFind / …同名同名
无限滚动useInfiniteScrolluseInfiniteScrolluseInfiniteScroll
异步操作useAction → Signal<boolean>useAction → ComputedRef<boolean>useAction → 渲染快照
持久化状态usePersistedState / useState → WritableSignal<T>usePersistedState → Ref<T>usePersistedState → 快照 + setValue
实体实时变更RxDBEntityChangeDirective(markForCheck)useEntityChangeuseEntityChange

时间窗判定(withTimeWindows)放在 @aiao/utils 里由三端共用。Vue / React 的持久化内核是 @aiao/utils 的 PersistedStateRegistry,与 Angular 的 root 服务 StateRegistry 键格式一致但各自持有内存状态 —— 同一页面里混用两端框架时,盘上数据互通,内存值不互通。

完整示例​

参考 dev-rxdb-angular 中的完整集成示例。

RxDB Angular 集成包 - Angular 框架的 RxDB 响应式数据库支持 提供 Angular 特有的响应式状态管理和变更检测集成

Classes​

ClassDescription
InfiniteScrollingList基于游标分页的无限滚动列表。
RxDBEntityChangeDirectiveRxDB 实体变化指令 如果你使用了 changeDetection: ChangeDetectionStrategy.OnPush 那么正在输入的变化不会触发 angular 渲染机制,使用这个指令可以实现实时渲染

Interfaces​

InterfaceDescription
ActionResourceuseAction 的返回值。
GraphPath图路径结果
InfiniteScrollResourceAngular 无限滚动资源。
NamespacedState某个 namespace + name 的状态入口。
NeighborResult图邻居查询结果(基础)
PersistedState某个 namespace + name 的持久化状态。
RxDBResource-
SyncStateResourceuseSyncState 的返回值。

Type Aliases​

Type AliasDescription
GraphQueryResult带资源截断状态的图查询数组。
InfiniteScrollOptionsAngular 无限滚动查询选项或响应式选项读取器。
RxDBSourceprovideRxDB 接受的数据库形态:实例、Promise,或返回二者之一的工厂。
UseOptions查询选项:可以是静态值,也可以是每次求值时调用的工厂函数。

Functions​

FunctionDescription
provideRxDB配置 Angular 注入器使用的 RxDB 实例。
useAction把一个异步函数包成带加载状态的可调用 action。
useCount统计满足条件的实体数量
useCountNeighbors统计图结构中的邻接数量
useFind查找符合条件的多个实体
useFindAll查找全部实体
useFindByCursor使用游标分页查找实体
useFindOne查找第一个匹配条件的实体
useFindOneOrFail查找匹配的实体,未找到则抛出错误
useGet通过 ID 获取单个实体
useGraphNeighbors查找图结构中的邻接实体
useGraphPaths查找图结构中两个实体之间的路径
useInfiniteScroll从当前 Angular 注入上下文创建无限滚动资源。
usePersistedState创建(或复用)一份持久化到 localStorage 的命名空间状态。
useRepositoryQuery将实体仓库的 Observable 查询转换为惰性 Angular Signal 资源。
useRxDB读取当前注入器中的 RxDB 实例。
useRxDBOptional读取当前注入器中的 RxDB 实例,未就绪时返回 undefined。
useState创建命名空间下的持久化状态读写入口。
useSyncState读取当前数据库的同步状态。