@fieldia/angular
Fieldia forms in Angular 21 and 22: a standalone component
over @fieldia/viewer, so a page looks and behaves the same as in any other
framework.
npm install @fieldia/angular
import { Component } from '@angular/core';
import { FieldiaFormComponent } from '@fieldia/angular';
import { createMemoryDataSource } from '@fieldia/core';
import page from './customer.page.json';
@Component({
selector: 'app-customer',
imports: [FieldiaFormComponent],
template: `<fieldia-form [page]="page" [dataSource]="dataSource" [recordId]="1" skin="underline" />`,
})
export class CustomerComponent {
readonly page = page;
readonly dataSource = createMemoryDataSource();
}
Custom fields can be Angular components (fieldTypes), slot content goes in
<ng-template fieldiaSlot="name">, and formState(form) gives you a form's state
as a signal. The viewer's options are inputs of the same names, but for the
app's translator: [translator], because a [translate] binding goes to the
element's own HTML attribute. A list page opens a row through the
(openRecord) output.
The form's events are outputs: (fieldChange) — not (change), which on a
component's element would hear its boxes' own change events too — (save),
(send), (step) and (run). An output cannot answer, so the app's answer to
a button or a call step is the [answer] input; (action) still tells every
call:
@Component({
imports: [FieldiaFormComponent],
template: `<fieldia-form [page]="page" [dataSource]="dataSource" [pages]="pages"
[answer]="answer" (fieldChange)="changed($event)" (save)="saved($event)" (run)="ran($event)" />`,
})
export class OrderComponent {
readonly answer = async ({ action, values }: ActionRequest) =>
action === 'check_stock' ? { values: { price: await priceOf(values['product']) } } : undefined;
}
The viewer's host and onOpen are [actionHost] and [openPage]; the
handle from (ready) has on, setValues and run.
A page with continueLater opens a draft kept on your server by its key —
[resume]="route.snapshot.queryParamMap.get('resume') ?? undefined", the key the
link Continue later gave — and (draft) is told of each draft kept. A new
[resume] opens that draft, as a new [recordId] opens that record.
A page that fills fields from its link (prefill) reads the browser's address;
an app rendered on a server, or shown inside another page, gives the link it
knows — [url]="router.url", an address, or [url]="{ agent: 'mona' }". A new
[url] starts the form from it again.
Before your app leaves the form, ask its one check: await handle.form.leave() in a route's canDeactivate, before removing
<fieldia-form>, and before changing an input that mounts it again (page,
form, dataSource, recordId, resume, url, user, context, dir, locale,
labels, fieldTypes, the names in functions). Angular destroys it at once, so nothing else can keep
the person there. It waits for a save on its way, then asks "Discard your
changes?" only when their work is at risk, and answers false when they choose to
stay. [warnOnLeave]="true" has the browser's own Back, Reload or a closed tab
ask first while there is a change not saved.
A part that throws as it is drawn or brought up to date — a field's component, a
slot's content, the app's widget, a map — shows a small box that names it, and
the rest of the form goes on; (partError) hears { error, where } (not
(error), the DOM's own event). A page the viewer refuses says why in the form's
place, (partError) hears it with { stage: 'draw', part: 'page' }, and the app
goes on; one it draws whose whole check finds mistakes, { stage: 'check', part: 'page', warning: true } (0.36; refused from 0.37). MIT licensed.