Minimal Setup
To get started quickly follow this minimal setup.
ngx-formwork comes with no pre-built components by design. This gives you flexibility of what framework to use and how to structure the markup. Furthermore, it uses the Directive Composition API instead of inheritance. While this may seem to make some things a little more verbose, it is the better approach to encapsulate the core logic.
Controls
Section titled “Controls”Here is an example of a simple text control.
First create an interface for your control.
export interface TextControl extends NgxFwControl { // Unique Key of your control that is used for differentiating controls // This can be descriptive like "email-control" type: 'text-control';
// Overwrite defaultValue with correct value type // the default value type is "unknown" defaultValue?: string;
// Additional options only applicable to this control hint?: string;}
Then implement the component.
Warning Be sure to bind to
[formControlName]
on the actual input element
@Component({ selector: 'app-text-control', imports: [ReactiveFormsModule], templateUrl: './text-control.component.html', viewProviders: [ { provide: ControlContainer, useFactory: () => inject(ControlContainer, { skipSelf: true }), } ], hostDirectives: [ { directive: NgxfwGroupDirective, inputs: ['content'], } ],})export class TextControlComponent { // Inject the Directive to gain access to all public properties // Make sure to pass the correct type parameter to get proper type information private readonly control = inject(NgxfwControlDirective<TextControl>);
// Explicitly setting a type definition is not required, but some IDEs work better if they are present readonly content: Signal<TextControl> = this.control.content; // The configuration object of the control instance
// We get proper type information when accessing this.content() readonly hint = computed(() => this.content().hint); readonly label = computed(() => this.content().label); readonly id = computed(() => this.content().id);}
<!-- Just an example --><label [htmlFor]="id()">{{ label() }}</label><input [id]="id()" [formControlName]="id()"/><span>{{hint()}}</span>
Finally, register the control in app.config.ts
componentRegistrations: { text: TextControlComponent}
Groups
Section titled “Groups”Here is an example of a simple group. Most likely you will only set up one or two group components, if at all.
First create an interface for your group.
export interface Group extends NgxFwFormGroup { // Unique Key of your group that is used for differentiating groups type: 'group';
// Additional options only applicable to this group if you want}
Then implement the component.
Warning Be sure to bind to
[formGroupName]
on an element (e.g. div, ng-container)
@Component({ selector: 'ngxfw-group', imports: [NgxfwAbstractControlDirective, ReactiveFormsModule], templateUrl: './group.component.html', viewProviders: [ { provide: ControlContainer, useFactory: () => inject(ControlContainer, { skipSelf: true }), }, ], hostDirectives: [ { directive: NgxfwGroupDirective, inputs: ['content'], } ],})export class GroupComponent { // Inject the Directive to gain access to all public properties // Make sure to pass the correct type parameter to get proper type information private readonly control = inject(NgxfwGroupDirective<Group>);
// Explicitly setting a type definition is not required, but some IDEs work better if they are present readonly content: Signal<Group> = this.control.content; // The configuration object of the group instance readonly id = computed(() => this.content().id); readonly controls: Signal<NgxFwContent[]> = this.control.controls;}
<!-- Just an example --><div [formGroupName]="id">@for (control of controls(); track control.id) { <ng-template *ngxfwNgxfwAbstractControl="control" />}</div>
Finally, register the group in app.config.ts
componentRegistrations: { group: GroupComponent}
Configuring a form
Section titled “Configuring a form”Once you’ve registered controls and optionally validators, you write a configuration for a form. You can either do this directly in JSON or in TypeScript, for better typing information.
This example is written in TypeScript
export const exampleForm: NgxFwContent[] = [ { type: 'text', id: 'name', label: 'First and Lastname', }, { type: 'text', id: 'company', label: 'Name of Company', hint: 'If applicable', }, { type: 'group', id: 'repo', controls: [ { type: 'text', id: 'username', label: 'Username', default: 'UsernameSuggestion123', }, ], },];
As you can see the configuration is just an array of controls and/or groups. Every item in that array will be registered on the top-level of the form.
Rendering a form
Section titled “Rendering a form”Now that everything is set up, you can render the form.
You build the form as usual. This gives you full access to the underlying form, and you can do everything you normally can too.
@Component({ selector: 'app-example-form', imports: [ReactiveFormsModule, NgxFwFormComponent], templateUrl: './example-form.component.html'})export class ExampleFormComponent { // Construct the reactive form as usual private readonly formBuilder = inject(FormBuilder);
// This is our form configuration. It doesn't have to be passed as an input. You could also have a service that gets this or just import it from a file. readonly formContent = input.required<NgxFwContent[]>();
// Building a form with an empty group. All controls and groups are self-registering // You can also add additional hardcoded control if you want form = this.formBuilder.group({});
// We have normal access to all properties of the form reset() { this.form.reset(); }
patchValue() { // Setting the value of the form is done the same way as you normally would this.form.patchValue({ // Whatever value we want to patch }); }}
<!-- Just normal form binding --><form [formGroup]="form" (ngSubmit)="onSubmit()">
<!-- This component renders your form --> <ngxfw-form [formContent]="formContent()" />
<!-- normal submit button --> <button type="submit">Submit</button></form>
<button type="button" (click)="reset()">Reset</button>