@aiao/rxdb-angular
v0.0.25
Published
Angular 框架集成库,为 Angular 应用提供 RxDB 支持。基于 Angular Signals 实现响应式数据流。
Readme
@aiao/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
import { provideRxDB } from '@aiao/rxdb-angular';
export const appConfig: ApplicationConfig = {
providers: [
// 传入返回 RxDB 实例的工厂函数
provideRxDB(() => rxdb)
]
};查询数据
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 是本仓库的硬约束,框架惯例允许容器形态与命名差异,不允许能力缺失:
| 能力 | Angular | Vue | React |
| ------------ | ------------------------------------------------------ | ------------------------------------ | --------------------------------------- |
| 查询 | useGet / useFind / … | 同名 | 同名 |
| 无限滚动 | useInfiniteScroll | useInfiniteScroll | useInfiniteScroll |
| 异步操作 | useAction → Signal<boolean> | useAction → ComputedRef<boolean> | useAction → 渲染快照 |
| 持久化状态 | usePersistedState / useState → WritableSignal<T> | usePersistedState → Ref<T> | usePersistedState → 快照 + setValue |
| 实体实时变更 | RxDBEntityChangeDirective(markForCheck) | useEntityChange | useEntityChange |
时间窗判定(withTimeWindows)放在 @aiao/utils 里由三端共用。Vue / React 的持久化内核是
@aiao/utils 的 PersistedStateRegistry,与 Angular 的 root 服务 StateRegistry
键格式一致但各自持有内存状态 —— 同一页面里混用两端框架时,盘上数据互通,内存值不互通。
完整示例
参考 dev-rxdb-angular 中的完整集成示例。
