Quick start
This walks through one complete feature: a Convex table, a query and a mutation on the backend, and an Angular component that renders the query live and writes through the mutation.
It assumes you have already installed the packages and registered
provideConvex().
1. Define the table
Section titled “1. Define the table”import { defineSchema, defineTable } from 'convex/server';import { v } from 'convex/values';
export default defineSchema({ todos: defineTable({ title: v.string(), completed: v.boolean(), }),});2. Write the backend functions
Section titled “2. Write the backend functions”A query reads data and is subscribable; a mutation writes data transactionally.
import { v } from 'convex/values';
import { mutation, query } from './_generated/server';
export const list = query({ args: { count: v.number() }, handler: async (ctx, args) => { return await ctx.db.query('todos').order('desc').take(args.count); },});
export const create = mutation({ args: { title: v.string() }, handler: async (ctx, args) => { return await ctx.db.insert('todos', { title: args.title, completed: false }); },});With convex dev running, saving this file deploys both functions and regenerates
convex/_generated/api.d.ts. The generated api object is what you pass to the helpers, and it is
what makes argument and return types flow into your component with no manual annotations.
3. Use them in a component
Section titled “3. Use them in a component”injectQuery() takes a function reference and a function returning the arguments. injectMutation()
takes a function reference and returns a mutate() method plus reactive state.
import { Component, signal } from '@angular/core';import { injectMutation, injectQuery } from 'convex-angular';
import { api } from './convex/api';
@Component({ selector: 'app-todo-list', template: ` <form (submit)="add($event)"> <input name="title" [value]="title()" (input)="title.set($any($event.target).value)" /> <button type="submit" [disabled]="createTodo.isLoading()"> {{ createTodo.isLoading() ? 'Adding…' : 'Add todo' }} </button> </form>
@if (createTodo.error(); as error) { <p role="alert">{{ error.message }}</p> }
@switch (todos.status()) { @case ('pending') { <p>Loading todos…</p> } @case ('error') { <p role="alert">{{ todos.error()?.message }}</p> } @case ('skipped') { <p>Nothing to load.</p> } @case ('success') { <ul> @for (todo of todos.data() ?? []; track todo._id) { <li>{{ todo.title }}</li> } @empty { <li>No todos yet.</li> } </ul> } } `,})export class TodoListComponent { readonly title = signal('');
// The argument function is reactive: reading `count()` inside it makes the // subscription follow the signal. readonly count = signal(20); readonly todos = injectQuery(api.todos.list, () => ({ count: this.count() }));
readonly createTodo = injectMutation(api.todos.create, { onSuccess: () => this.title.set(''), });
async add(event: Event): Promise<void> { event.preventDefault(); // No manual refetch: the live query subscription pushes the new todo. await this.createTodo.mutate({ title: this.title() }); }}What the pieces do
Section titled “What the pieces do”| Expression | Meaning |
|---|---|
todos.status() |
'pending' | 'success' | 'error' | 'skipped' — the single value to branch on. |
todos.data() |
The query result, or undefined before the first value arrives. |
todos.error() |
The subscription error, or undefined. |
todos.isRefetching() |
True while resubscribing with a previous value still shown — render a subtle spinner. |
todos.refetch() |
Force a resubscribe. |
createTodo.mutate(args) |
Runs the mutation; resolves with its return value, rejects on failure. |
createTodo.isLoading() |
True while the mutation is in flight. |
createTodo.error() |
The last mutation error, or undefined. |
createTodo.reset() |
Clears data, error, and returns the status to 'idle'. |
Where to go next
Section titled “Where to go next”- Providers — where
provideConvex()may be registered, and what it sets up. - Reactivity — how reactive arguments decide when to resubscribe.
- Status and state — the exact status derivations and their gotchas.
- Error handling — signals, promise rejections, and
ConvexError.