Skip to content

Navigation Menu

Sign in
Appearance settings

Search code, repositories, users, issues, pull requests...

Provide feedback

We read every piece of feedback, and take your input very seriously.

Saved searches

Use saved searches to filter your results more quickly

Appearance settings

Latest commit

 

History

History
History

@tanstack/angular-db

Angular hooks for TanStack DB. See TanStack/db for more details.

Installation

npm install @tanstack/angular-db @tanstack/db

Usage

Basic Setup

First, create a collection:

import { createCollection, localOnlyCollectionOptions } from "@tanstack/db"

interface Todo {
  id: number
  text: string
  completed: boolean
  projectID: number
  created_at: Date
}

export const todosCollection = createCollection(
  localOnlyCollectionOptions<Todo>({
    getKey: (todo: Todo) => todo.id,
    initialData: [
      {
        id: 1,
        text: "Learn Angular",
        completed: false,
        projectID: 1,
        created_at: new Date(),
      },
    ],
  })
)

Using injectLiveQuery in Components

Direct Collection Usage

The simplest way to use injectLiveQuery is to pass a collection directly:

import { Component } from "@angular/core"
import { injectLiveQuery } from "@tanstack/angular-db"
import { todosCollection } from "./collections/todos-collection"

@Component({
  selector: "app-all-todos",
  template: `
    @if (allTodos.isReady()) {
      <div>Total todos: {{ allTodos.data().length }}</div>
      @for (todo of allTodos.data(); track todo.id) {
        <div>{{ todo.text }}</div>
      }
    } @else {
      <div>Loading todos...</div>
    }
  `,
})
export class AllTodosComponent {
  // Direct collection usage - gets all items
  allTodos = injectLiveQuery(todosCollection)
}

Static Query Functions

You can create filtered queries using a query function. Note: The query function is evaluated once and is not reactive to signal changes:

import { Component } from "@angular/core"
import { injectLiveQuery } from "@tanstack/angular-db"
import { eq } from "@tanstack/db"
import { todosCollection } from "./collections/todos-collection"

@Component({
  selector: "app-todos",
  template: `
    @if (todoQuery.isReady()) {
      @for (todo of todoQuery.data(); track todo.id) {
        <div class="todo-item">
          {{ todo.text }}
          <button (click)="toggleTodo(todo.id)">
            {{ todo.completed ? "Undo" : "Complete" }}
          </button>
        </div>
      }
    } @else {
      <div>Loading todos...</div>
    }
  `,
})
export class TodosComponent {
  // Static query - filters for incomplete todos
  // This will not react to signal changes within the function
  todoQuery = injectLiveQuery((q) =>
    q
      .from({ todo: todosCollection })
      .where(({ todo }) => eq(todo.completed, false))
  )

  toggleTodo(id: number) {
    todosCollection.utils.begin()
    todosCollection.utils.write({
      type: 'update',
      key: id,
      value: { completed: true }
    })
    todosCollection.utils.commit()
  }
}

Reactive Queries with Parameters

For queries that need to react to component state changes, use the reactive parameters overload:

import { Component, signal } from "@angular/core"
import { injectLiveQuery } from "@tanstack/angular-db"
import { eq } from "@tanstack/db"
import { todosCollection } from "./collections/todos-collection"

@Component({
  selector: "app-project-todos",
  template: `
    <select (change)="selectedProjectId.set(+$any($event).target.value)">
      <option [value]="1">Project 1</option>
      <option [value]="2">Project 2</option>
      <option [value]="3">Project 3</option>
    </select>

    @if (todoQuery.isReady()) {
      <div>Todos for project {{ selectedProjectId() }}:</div>
      @for (todo of todoQuery.data(); track todo.id) {
        <div class="todo-item">
          {{ todo.text }}
        </div>
      }
    } @else {
      <div>Loading todos...</div>
    }
  `,
})
export class ProjectTodosComponent {
  selectedProjectId = signal(1)

  // Reactive query - automatically recreates when selectedProjectId changes
  todoQuery = injectLiveQuery({
    params: () => ({ projectID: this.selectedProjectId() }),
    query: ({ params, q }) =>
      q
        .from({ todo: todosCollection })
        .where(({ todo }) => eq(todo.completed, false))
        .where(({ todo }) => eq(todo.projectID, params.projectID)),
  })
}

Advanced Configuration

You can also pass a full configuration object:

import { Component } from "@angular/core"
import { injectLiveQuery } from "@tanstack/angular-db"
import { eq } from "@tanstack/db"
import { todosCollection } from "./collections/todos-collection"

@Component({
  selector: "app-configured-todos",
  template: `
    @if (todoQuery.isReady()) {
      @for (todo of todoQuery.data(); track todo.id) {
        <div>{{ todo.text }}</div>
      }
    }
  `,
})
export class ConfiguredTodosComponent {
  todoQuery = injectLiveQuery({
    query: (q) =>
      q
        .from({ todo: todosCollection })
        .where(({ todo }) => eq(todo.completed, false))
        .select(({ todo }) => ({
          id: todo.id,
          text: todo.text,
        })),
    startSync: true,
    gcTime: 5000,
  })
}

Important Notes

Reactivity Behavior

  • Direct collection: Automatically reactive to collection changes
  • Static query function: Query is built once and is not reactive to signals read within the function
  • Reactive parameters: Query rebuilds when any signal read in params() changes
  • Collection configuration: Static, not reactive to external signals

Lifecycle Management

  • injectLiveQuery automatically handles subscription cleanup when the component is destroyed
  • Each call to injectLiveQuery creates a new collection instance (no caching/reuse)
  • Collections are started immediately and will sync according to their configuration

Template Usage

Use Angular's new control flow syntax for best performance:

@if (query.isReady()) {
  @for (item of query.data(); track item.id) {
    <div>{{ item.text }}</div>
  }
} @else if (query.isError()) {
  <div>Error loading data</div>
} @else {
  <div>Loading...</div>
}

API

injectLiveQuery()

Angular injection function for TanStack DB live queries. Must be called within an injection context (e.g., component constructor, inject(), or field initializer).

Overloads

// Direct collection - reactive to collection changes
function injectLiveQuery<TResult, TKey, TUtils>(
  collection: Collection<TResult, TKey, TUtils>
): LiveQueryResult<TResult>

// Static query function - NOT reactive to signals within function
function injectLiveQuery<TContext>(
  queryFn: (q: InitialQueryBuilder) => QueryBuilder<TContext>
): LiveQueryResult<TContext>

// Reactive query with parameters - recreates when params() signals change
function injectLiveQuery<TContext, TParams>(options: {
  params: () => TParams
  query: (args: {
    params: TParams
    q: InitialQueryBuilder
  }) => QueryBuilder<TContext>
}): LiveQueryResult<TContext>

// Collection configuration - static configuration
function injectLiveQuery<TContext>(
  config: LiveQueryCollectionConfig<TContext>
): LiveQueryResult<TContext>

Returns

An object with Angular signals:

  • data: Signal<Array> - Array of query results, automatically updates
  • state: Signal<Map<Key, T>> - Map of results by key, automatically updates
  • collection: Signal - The underlying collection instance
  • status: Signal - Current status ('idle' | 'loading' | 'ready' | 'error' | 'cleaned-up')
  • isLoading: Signal - true when status is 'loading'
  • isReady: Signal - true when status is 'ready'
  • isIdle: Signal - true when status is 'idle'
  • isError: Signal - true when status is 'error'
  • isCleanedUp: Signal - true when status is 'cleaned-up'

Parameters

  • collection - Existing collection to observe directly
  • queryFn - Function that builds a static query using the query builder
  • options.params - Reactive function that returns parameters; triggers query rebuild when accessed signals change
  • options.query - Function that builds a query using parameters and query builder
  • config - Configuration object for creating a live query collection

Requirements

  • Angular 16+ (requires signals support)
  • Must be called within an Angular injection context
  • Automatically handles cleanup when the injector is destroyed
Morty Proxy This is a proxified and sanitized view of the page, visit original site.