Skip to main content Home Creators tanstack db angular-db
angular-db Angular bindings for TanStack DB. injectLiveQuery inject function with Angular signals (Signal<T>) for all return values. Reactive params pattern ({ params: () => T, query: ({ params, q }) => QueryBuilder }) for dynamic queries. Must be called in injection context. Angular 17+ control flow (@if, @for) and signal inputs supported. Import from @tanstack/angular-db (re-exports all of @tanstack/db).
Jump to install Skills Marketplace Discover and explore AI skills built by the community.
Install with Codex or Claude Copy this prompt, paste it into Codex, Claude, or another assistant, and let it review the skill page and install it for you.
Copy promptShow prompt details A direct command skips the review prompt. Inspect the source before running it.
npx skills add https://github.com/TanStack/db --skill angular-dbThe command stays on one line. Scroll horizontally to inspect it before copying.
Prefer a local copy? Download the files currently available to SkillsMP.
Download Zip Downloading... More from this repository Creating typed collections with createCollection. Adapter selection: queryCollectionOptions (REST/TanStack Query), electricCollectionOptions (ElectricSQL real-time sync), powerSyncCollectionOptions (PowerSync SQLite), rxdbCollectionOptions (RxDB), trailBaseCollectionOptions (TrailBase), localOnlyCollectionOptions, localStorageCollectionOptions. CollectionConfig options: getKey, schema, sync, gcTime, autoIndex (default off), defaultIndexType, syncMode (eager/on-demand, plus progressive for Electric). StandardSchema validation with Zod/Valibot/ArkType. Collection lifecycle (idle/loading/ready/error). Adapter-specific sync patterns including Electric txid tracking, Query direct writes, Query initial data and scoped factories, and PowerSync query-driven sync with onLoad/onLoadSubset hooks.
Building custom collection adapters for new backends. SyncConfig interface: sync function receiving begin, write, commit, markReady, truncate, metadata primitives and returning cleanup, loadSubset, and optional unloadSubset handlers. ChangeMessage format (insert, update, delete). On-demand LoadSubsetOptions (where, orderBy, limit, offset, cursor). Expression parsing: parseWhereExpression, parseOrderByExpression, extractSimpleComparisons, parseLoadSubsetOptions. Collection options creator pattern. rowUpdateMode (partial vs full). Subscription lifecycle and cleanup functions. Persisted sync metadata API (metadata.row and metadata.collection) for storing per-row and per-collection adapter state.
Query builder fluent API: from, where, join, leftJoin, rightJoin, innerJoin, fullJoin, select, fn.select, groupBy, having, orderBy, limit, offset, distinct, findOne. Operators: eq, gt, gte, lt, lte, like, ilike, inArray, isNull, isUndefined, and, or, not. Aggregates: count, sum, avg, min, max. String functions: upper, lower, length, concat. Utility: coalesce, caseWhen. Math: add, subtract, multiply, divide. $selected namespace. createLiveQueryCollection. Derived collections. Predicate push-down. Incremental view maintenance via differential dataflow (d2ts). Virtual properties ($synced, $origin, $key, $collectionId). Includes subqueries for hierarchical data. Collection, toArray, materialize, and concat(toArray(...)) include modes. queryOnce for one-shot queries. createEffect for reactive side effects (onEnter, onUpdate, onExit, onBatch).
name angular-db description Angular bindings for TanStack DB. injectLiveQuery inject function with Angular signals (Signal<T>) for all return values. Reactive params pattern ({ params: () => T, query: ({ params, q }) => QueryBuilder }) for dynamic queries. Must be called in injection context. Angular 17+ control flow (@if, @for) and signal inputs supported. Import from @tanstack/angular-db (re-exports all of @tanstack/db).
type framework library db framework angular library_version 0.6.17 requires ["db-core"] sources ["TanStack/db:docs/framework/angular/overview.md","TanStack/db:packages/angular-db/src/index.ts"]
This skill builds on db-core. Read it first for collection setup, query builder, and mutation patterns.
TanStack DB โ Angular
Setup
import { Component } from '@angular/core'
import { injectLiveQuery, eq, not } from '@tanstack/angular-db'
@Component ({
selector : 'app-todo-list' ,
standalone : true ,
template : `
@if (query.isLoading()) {
<div>Loading...</div>
} @else {
<ul>
@for (todo of query.data(); track todo.id) {
<li>{{ todo.text }}</li>
}
</ul>
}
` ,
})
export class TodoListComponent {
query = injectLiveQuery ((q ) =>
q
.from ({ todos : todosCollection })
.where (({ todos } ) => not (todos.completed ))
.orderBy (({ todos } ) => todos.created_at , 'asc' ),
)
}
@tanstack/angular-db re-exports everything from @tanstack/db.
Inject Function
injectLiveQuery
Returns an object with Angular Signal<T> properties โ call with () in templates:
const query = injectLiveQuery ((q ) => q. ({ : todoCollection }))
query = ({
: ({ : . () }),
:
q
. ({ : todoCollection })
. ( (todo. , params. )),
})
query = ({
: q. ({ : todoCollection }),
: ,
})
query = (preloadedCollection)
query = ({
: ({ : . () }),
: {
(!params. )
q
. ({ : todoCollection })
. ( (todo. , params. ))
},
})
from
todo
const
injectLiveQuery
params
() =>
minPriority
this
minPriority
query
({ params, q } ) =>
from
todo
where
({ todo } ) =>
gt
priority
minPriority
const
injectLiveQuery
query
(q ) =>
from
todo
gcTime
60000
const
injectLiveQuery
const
injectLiveQuery
params
() =>
userId
this
userId
query
({ params, q } ) =>
if
userId
return
undefined
return
from
todo
where
({ todo } ) =>
eq
userId
userId
A bare { query } config defaults to startSync: true and gcTime: 0, like
the query-function overload. Explicit values in the config override those
defaults.
Angular-Specific Patterns
Reactive params with signals @Component ({
selector : 'app-filtered-todos' ,
standalone : true ,
template : `<div>{{ query.data().length }} todos</div>` ,
})
export class FilteredTodosComponent {
minPriority = signal (5 )
query = injectLiveQuery ({
params : () => ({ minPriority : this .minPriority () }),
query : ({ params, q } ) =>
q
.from ({ todos : todosCollection })
.where (({ todos } ) => gt (todos.priority , params.minPriority )),
})
}
When params() return value changes, the previous collection is disposed and a new query is created.
Signal inputs (Angular 17+) @Component ({
selector : 'app-user-todos' ,
standalone : true ,
template : `<div>{{ query.data().length }} todos</div>` ,
})
export class UserTodosComponent {
userId = input.required <number >()
query = injectLiveQuery ({
params : () => ({ userId : this .userId () }),
query : ({ params, q } ) =>
q
.from ({ todo : todoCollection })
.where (({ todo } ) => eq (todo.userId , params.userId )),
})
}
Legacy @Input (Angular 16) export class UserTodosComponent {
@Input ({ required : true }) userId!: number
query = injectLiveQuery ({
params : () => ({ userId : this .userId }),
query : ({ params, q } ) =>
q
.from ({ todo : todoCollection })
.where (({ todo } ) => eq (todo.userId , params.userId )),
})
}
Template syntax Angular 17+ control flow:
@if (query.isLoading()) {
<div > Loading...</div >
} @else { @for (todo of query.data(); track todo.id) {
<li > {{ todo.text }}</li >
} }
Angular 16 structural directives:
<div *ngIf ="query.isLoading()" > Loading...</div >
<li *ngFor ="let todo of query.data(); trackBy: trackById" > {{ todo.text }}</li >
Includes (Hierarchical Data) When a query uses includes (subqueries in select), each child field is a live Collection by default. Subscribe to it with injectLiveQuery in a child component:
@Component ({
selector : 'app-project-list' ,
standalone : true ,
imports : [IssueListComponent ],
template : `
@for (project of query.data(); track project.id) {
<div>
{{ project.name }}
<app-issue-list [issuesCollection]="project.issues" />
</div>
}
` ,
})
export class ProjectListComponent {
query = injectLiveQuery ((q ) =>
q.from ({ p : projectsCollection }).select (({ p } ) => ({
id : p.id ,
name : p.name ,
issues : q
.from ({ i : issuesCollection })
.where (({ i } ) => eq (i.projectId , p.id ))
.select (({ i } ) => ({ id : i.id , title : i.title })),
})),
)
}
@Component ({
selector : 'app-issue-list' ,
standalone : true ,
template : `
@for (issue of query.data(); track issue.id) {
<li>{{ issue.title }}</li>
}
` ,
})
export class IssueListComponent {
issuesCollection = input.required <Collection >()
query = injectLiveQuery (this .issuesCollection ())
}
With toArray(), child results are plain arrays and the parent re-emits on child changes:
import { toArray, eq } from '@tanstack/angular-db'
query = injectLiveQuery ((q ) =>
q.from ({ p : projectsCollection }).select (({ p } ) => ({
id : p.id ,
name : p.name ,
issues : toArray (
q
.from ({ i : issuesCollection })
.where (({ i } ) => eq (i.projectId , p.id ))
.select (({ i } ) => ({ id : i.id , title : i.title })),
),
})),
)
See db-core/live-queries/SKILL.md for full includes rules (correlation conditions, nested includes, aggregates).
Common Mistakes
CRITICAL Using injectLiveQuery outside injection context export class TodoComponent {
ngOnInit ( ) {
this .query = injectLiveQuery ((q ) => q.from ({ todo : todoCollection }))
}
}
export class TodoComponent {
query = injectLiveQuery ((q ) => q.from ({ todo : todoCollection }))
}
injectLiveQuery calls assertInInjectionContext internally โ it must be called during construction (field initializer or constructor), not in lifecycle hooks.
Source: packages/angular-db/src/index.ts
HIGH Using query function for reactive values instead of params export class FilteredComponent {
status = signal ('active' )
query = injectLiveQuery ((q ) =>
q
.from ({ todo : todoCollection })
.where (({ todo } ) => eq (todo.status , this .status ())),
)
}
export class FilteredComponent {
status = signal ('active' )
query = injectLiveQuery ({
params : () => ({ status : this .status () }),
query : ({ params, q } ) =>
q
.from ({ todo : todoCollection })
.where (({ todo } ) => eq (todo.status , params.status )),
})
}
The plain query function overload does not track Angular signal reads. Use the params pattern to make reactive values trigger query re-creation.
Source: packages/angular-db/src/index.ts
MEDIUM Forgetting to call signals in templates <div > {{ query.data.length }}</div >
<div > {{ query.data().length }}</div >
All return values are Angular signals. Without (), you get the signal object, not the value.
See also: db-core/live-queries/SKILL.md โ for query builder API.
See also: db-core/mutations-optimistic/SKILL.md โ for mutation patterns.