Fetch Form
Dynamic Form Replacement
This demo shows how to build a multi-step / conditional form that completely replaces its fields based on user selection — using a fetch trigger in 'replace' mode.
What it does
- Starts with a single product selector dropdown.
- When the user picks "Product A" or "Product B":
- A
fetchtrigger fires onCHANGE. - The custom
DYNAMIC_FORM_FETCHERsimulates an API call to/api/forms/{selectedValue}. - It returns a new array of fields tailored to the chosen product.
mode: 'replace'overwrites the entire form content with the returned fields.- Result: The form dynamically switches between different input sets (e.g. "Feature A + Quantity" vs "Color + Units").
Key Features
- Form-level replacement via
mode: 'replace'on fetch trigger - Returning full field definitions from the fetcher (not just patches)
- Self-referencing trigger on the
SelectField(fetchUrl: '/api/forms/$value') - Keeping the selected value preserved by including the
SelectFieldin every response - Clean simulation of backend-driven conditional forms
Code Highlights
// Trigger on the initial SelectField
triggers: [{
on: FormFieldEventType.CHANGE,
action: TriggerAction.FETCH,
fetchUrl: '/api/forms/$value', // $value = current selection
mode: 'replace'
}]
// Fetcher returns different field sets
if (selection === 'productA') {
return of([
selectField, // preserve the selector
new TextField({ key: 'productA_feature', ... }),
new NumberField({ key: 'productA_quantity', ... }),
]);
}
Ideal for wizards, product configurators, onboarding flows, or any form where the structure changes significantly based on early choices — all without manual component swapping or complex *ngIf logic. Perfect example of how @preforms/angular turns static forms into fully dynamic, backend-driven experiences.
Live Demo
Result
TS
import { Component, EventEmitter, Output, ViewEncapsulation } from '@angular/core';
import { Preforms } from '@preforms/angular/core';
import { DYNAMIC_FORM_FETCHER } from '@preforms/angular/core';
import { NATIVE_FORM_ELEMENTS } from '@preforms/angular/native';
import {
FormElement,
FormFieldEventType,
NumberField,
SelectField,
SubmitButton,
TextField,
TriggerAction,
} from '@preforms/ts';
import { Observable, of } from 'rxjs';
@Component({
selector: 'app-dynamic-form-fetch',
template: `<preforms (submittedData)="logData($event)" [fields]="fields" />`,
imports: [Preforms],
encapsulation: ViewEncapsulation.None,
providers: [
NATIVE_FORM_ELEMENTS,
{
provide: DYNAMIC_FORM_FETCHER,
useValue: (url: string): Observable<Partial<FormElement>[]> => {
// simulate API returning fields based on selection
const selection = url.split('/').pop();
const selectField = new SelectField({
key: 'product',
label: 'Select Product',
value: selection,
options: [
{ label: 'Product A', value: 'productA' },
{ label: 'Product B', value: 'productB' },
],
triggers: [
{
on: FormFieldEventType.CHANGE,
action: TriggerAction.FETCH,
fetchUrl: '/api/forms/$value',
mode: 'replace', // replace the current form with fetched fields
},
],
});
if (selection === 'productA') {
return of([
selectField,
new TextField({
key: 'productA_feature',
label: 'Feature A',
placeholder: 'Enter feature A',
required: true,
}),
new NumberField({
key: 'productA_quantity',
label: 'Quantity',
min: 1,
value: 1,
}),
]);
}
if (selection === 'productB') {
return of([
selectField,
new TextField({
key: 'productB_color',
label: 'Color',
placeholder: 'Enter color',
required: true,
}),
new NumberField({
key: 'productB_units',
label: 'Units',
min: 1,
value: 1,
}),
]);
}
return of([]);
},
},
],
})
export class DynamicFormFetchComponent {
@Output() formChange = new EventEmitter<any>();
fields = [
new SelectField({
key: 'product',
label: 'Select Product',
options: [
{ label: 'Product A', value: 'productA' },
{ label: 'Product B', value: 'productB' },
],
triggers: [
{
on: FormFieldEventType.CHANGE,
action: TriggerAction.FETCH,
fetchUrl: '/api/forms/$value',
mode: 'replace', // replace the current form with fetched fields
},
],
}),
new TextField({
key: 'productA_feature',
label: 'Feature A',
placeholder: 'Enter feature A',
required: true,
}),
new NumberField({
key: 'productA_quantity',
label: 'Quantity',
min: 1,
value: 1,
}),
new SubmitButton(),
];
logData(data: any) {
this.formChange.emit(data);
}
}