| name | momentum-api |
| description | Work with Momentum API for data operations in Angular components |
| argument-hint | <operation> [collection] |
Momentum API Usage
Guide for using injectMomentumAPI() in Angular components.
Key rules: Always use async/await (never subscribe). Always use instanceof checks for DOM elements (never as type assertions). Always use typed error classes for error handling.
Arguments
$ARGUMENTS - Operation type: "query", "crud", "typed", or collection name
Quick Reference
Inject the API
import { injectMomentumAPI } from '@momentumcms/admin';
@Component({...})
export class MyComponent {
private readonly api = injectMomentumAPI();
}
Query Data (async/await — the only correct pattern)
Always use async/await with .find() and .findById(). Never use .find$().subscribe() or any Observable pattern.
async loadData(): Promise<void> {
const result = await this.api.collection<Post>('posts').find({ limit: 10 });
this.posts.set(result.docs);
}
Single Document Lookup
Use .findById() to fetch a single document by ID:
async loadPost(id: string): Promise<void> {
const post = await this.api.collection<Post>('posts').findById(id);
this.post.set(post);
}
CRUD Operations
const post = await this.api.collection<Post>('posts').create({ title: 'New Post' });
const post = await this.api.collection<Post>('posts').findById('123');
const updated = await this.api.collection<Post>('posts').update('123', { title: 'Updated' });
const result = await this.api.collection<Post>('posts').delete('123');
With Generated Types
- Generate types:
nx run example-angular:generate-types
- Import and use:
import type { Post, User } from '../types/momentum.generated';
const posts = await this.api.collection<Post>('posts').find();
const users = await this.api.collection<User>('users').find();
Find Options
Always pass FindOptions to .find() to control queries. All fields are optional:
interface FindOptions {
where?: Record<string, unknown>;
sort?: string;
limit?: number;
page?: number;
depth?: number;
transfer?: boolean;
}
FindOptions Usage Examples
const published = await this.api.collection<Post>('posts').find({
where: { status: { equals: 'published' } },
limit: 20,
sort: '-createdAt',
page: 1,
});
const page2 = await this.api.collection<Post>('posts').find({
limit: 10,
page: 2,
sort: 'title',
});
const filtered = await this.api.collection<Post>('posts').find({
where: { category: { equals: categoryId }, _status: { equals: 'published' } },
limit: 50,
sort: '-createdAt',
});
Do / Don't
DOM Access — never use as type assertions
async handleSubmit(event: Event): Promise<void> {
const form = event.target as HTMLFormElement;
const input = form.querySelector('input') as HTMLInputElement;
}
async handleSubmit(event: Event): Promise<void> {
event.preventDefault();
const target = event.target;
if (!(target instanceof HTMLFormElement)) return;
const input = target.querySelector('input');
if (!(input instanceof HTMLInputElement)) return;
const post = await this.api.collection<Post>('posts').create({
title: input.value,
});
this.posts.update((posts) => [post, ...posts]);
input.value = '';
}
Data Fetching — always use async/await, never subscribe
this.api
.collection<Post>('posts')
.find$({ limit: 10 })
.subscribe((result) => {
this.posts.set(result.docs);
});
const result = await this.api.collection<Post>('posts').find({ limit: 10 });
this.posts.set(result.docs);
Error Handling — use error name checks (browser-safe)
Important: Error classes live in @momentumcms/server-core (env:server).
Browser components MUST NOT import from server packages. Use error.name checks instead.
try {
await this.api.collection('posts').create(data);
} catch (e) {
console.error(e);
}
interface MomentumError extends Error {
errors?: Array<{ field: string; message: string }>;
}
try {
await this.api.collection<Post>('posts').create(data);
} catch (error) {
const err = error as MomentumError;
if (err.name === 'ValidationError' && err.errors) {
this.validationErrors.set(err.errors);
} else if (err.name === 'DocumentNotFoundError') {
this.notFound.set(true);
} else if (err.name === 'AccessDeniedError') {
this.accessDenied.set(true);
}
}
Full Component Example
import { Component, signal, ChangeDetectionStrategy } from '@angular/core';
import { injectMomentumAPI } from '@momentumcms/admin';
interface MomentumError extends Error {
errors?: Array<{ field: string; message: string }>;
}
import type { Post } from '../types/momentum.generated';
@Component({
selector: 'app-posts',
template: `
@if (loading()) {
<p>Loading...</p>
} @else if (error()) {
<p>{{ error() }}</p>
} @else {
@for (post of posts(); track post.id) {
<article>
<h2>{{ post.title }}</h2>
<p>{{ post.content }}</p>
<button (click)="deletePost(post.id)">Delete</button>
</article>
}
}
<form (submit)="createPost($event)">
<input #titleInput placeholder="Title" />
<button type="submit">Create Post</button>
</form>
`,
changeDetection: ChangeDetectionStrategy.OnPush,
})
export class PostsComponent {
private readonly api = injectMomentumAPI();
readonly posts = signal<Post[]>([]);
readonly loading = signal(true);
readonly error = signal<string | null>(null);
constructor() {
void this.loadPosts();
}
async loadPosts(): Promise<void> {
this.loading.set(true);
try {
const result = await this.api.collection<Post>('posts').find({
limit: 20,
sort: '-createdAt',
where: { _status: { equals: 'published' } },
});
this.posts.set(result.docs);
} catch (error) {
const err = error as MomentumError;
if (err.name === 'DocumentNotFoundError') {
this.error.set('Posts collection not found.');
} else {
this.error.set('Failed to load posts.');
}
} finally {
this.loading.set(false);
}
}
async createPost(event: Event): Promise<void> {
event.preventDefault();
const target = event.target;
if (!(target instanceof HTMLFormElement)) return;
const input = target.querySelector('input');
if (!(input instanceof HTMLInputElement)) return;
try {
const post = await this.api.collection<Post>('posts').create({
title: input.value,
});
this.posts.update((posts) => [post, ...posts]);
input.value = '';
} catch (error) {
const err = error as MomentumError;
if (err.name === 'ValidationError' && err.errors) {
console.error('Validation failed:', err.errors);
}
}
}
async deletePost(id: string): Promise<void> {
await this.api.collection<Post>('posts').delete(id);
this.posts.update((posts) => posts.filter((p) => p.id !== id));
}
}
Error Handling
Use error.name checks for browser-safe error handling (do NOT import from @momentumcms/server-core in browser code):
interface MomentumError extends Error {
errors?: Array<{ field: string; message: string }>;
}
try {
await this.api.collection<Post>('posts').findById(id);
} catch (error) {
const err = error as MomentumError;
if (err.name === 'DocumentNotFoundError') {
this.notFound.set(true);
} else if (err.name === 'AccessDeniedError') {
this.accessDenied.set(true);
} else if (err.name === 'ValidationError' && err.errors) {
this.validationErrors.set(err.errors);
} else if (err.name === 'CollectionNotFoundError') {
this.error.set('Invalid collection');
}
}
Available Error Classes
| Error Class | When Thrown | Useful Properties |
|---|
ValidationError | Create/update with invalid data | errors: { field, message }[] |
DocumentNotFoundError | findById with non-existent ID | message |
AccessDeniedError | User lacks permission for operation | message |
CollectionNotFoundError | Invalid collection slug | message |
GlobalNotFoundError | Invalid global slug | message |
Platform Behavior
- SSR: Direct database access (no HTTP overhead)
- Browser: HTTP calls to
/api/*
- Same interface - code works identically on both platforms
Type Generation
Generate types from your collections:
nx run example-angular:generate-types
nx run example-angular:generate-types --watch
Output file: src/types/momentum.generated.ts
TransferState (SSR Hydration)
TransferState is enabled by default for all read operations (find, findById, findSignal, findByIdSignal). Data fetched during SSR is automatically cached and reused on browser hydration, eliminating duplicate HTTP calls.
Default Behavior (TransferState enabled)
const posts = await this.api.collection<Post>('posts').find({ limit: 10 });
const post = await this.api.collection<Post>('posts').findById(id);
Opt-out
Use transfer: false to disable TransferState for a specific call:
const posts = await this.api.collection<Post>('posts').find({
limit: 10,
transfer: false,
});
Signal Methods
readonly posts = this.api.collection<Post>('posts').findSignal({ limit: 10 });
readonly post = this.api.collection<Post>('posts').findByIdSignal(id);
Requirements
Ensure provideClientHydration() is in your app config:
export const appConfig: ApplicationConfig = {
providers: [provideClientHydration()],
};