README.md

June 24, 2026 · View on GitHub

ng-modular-forms logo

ng-modular-forms

A structured Angular forms architecture built for complex, scalable applications.

CI/CD Angular Version npm version License: MIT

Live Examples & Interactive Demo →

The Problem

As enterprise Angular applications grow, form logic quickly becomes a maintenance bottleneck. Side-effect subscriptions spread across components, API data mapping logic gets duplicated, and components bloat with validation mechanics.

The Solution

ng-modular-forms introduces a strict separation of concerns layer built right on top of Angular Reactive Forms. It isn't a replacement for standard reactive APIs — it's an architectural framework designed to decouple:

  • Form Orchestration: Isolate conditional validation and multi-step workflow logic.
  • Reactive Behaviors: Pull complex cross-field dependencies out of presentation components.
  • Data Mapping: Translate backend DTOs to form states declaratively.
  • UI Shells: Interchange native elements and Angular Material layers instantly.

Key Features

  • Typed Reactive Forms support
  • Form orchestration layer
  • DTO ↔ Form mapping
  • State hydration and serialization
  • Cross-field behavior management
  • Dynamic enable/disable workflows
  • Native and Angular Material UI packages
  • Consistent component APIs
  • Enterprise-scale architecture patterns
  • Angular 19–21 support

Installation & Setup

1. Install Packages

Start with core:

npm install @ng-modular-forms/core

If you are using Angular Material components, install the UI adapter and its peer dependencies:

npm install @ng-modular-forms/material @angular/material @angular/cdk

2. Configure Global Styles

Add the required control structural themes to your angular.json styles pipeline depending on your configuration:

"styles": [
  "src/styles.css",
  
  // Required ONLY if utilizing @ng-modular-forms/core native UI elements
  "node_modules/@ng-modular-forms/core/styles/form-controls.css",
  
  // Required ONLY if utilizing @ng-modular-forms/material UI components
  "node_modules/@ng-modular-forms/material/styles/form-controls.css"
]

3. Configure Global Configuration

@ng-modular-forms/core provides a single global configuration system via provideNmfConfig.

This configuration is shared across all packages (core + material) and is optional.

If not provided, sensible defaults are used.

Available options

translate?: (
  key: string,
  params?: Record<string, unknown>
) => string;

validationMessages?: ValidationMessages;

Code Example

import { ApplicationConfig, inject } from '@angular/core';
import { TranslateService } from '@ngx-translate/core';
import { provideNmfConfig } from '@ng-modular-forms/core';

export const appConfig: ApplicationConfig = {
  providers: [
    // Simple config
    provideNmfConfig({
      validationMessages: {
        email: 'Invalid email',
      },
    })

    //DI-based config
    provideNmfConfigFactory(() => {
      const translate = inject(TranslateService);

      return {
        translate: (k, p) => translate.instant(k, p),
        validationMessages: {},
      };
    });
  ],
};

Code Examples

Start Simple

@Component({
  template: `
    <form [formGroup]="form">
      <nmf-text
        label="First Name"
        formControlName="firstName"
      />

      <nmf-number
        label="Salary"
        formControlName="salary"
        [formatValue]="true"
        prefix="$"
        suffix="USD"
      />
    </form>
  `,
})
export class EmployeeComponent {
  form = new FormGroup({
    firstName: new FormControl('', Validators.required),
    salary: new FormControl<number | null>(null),
  });
}

Start with standard Angular Reactive Forms and adopt orchestration features only when your application needs them.

Add Reactive Logic

As forms grow, reactive business rules often end up scattered throughout Angular components.

ng-modular-forms allows reactive behavior to be moved into dedicated handlers.

@Injectable()
export class RegistrationFormHandler extends FormHandlerBase<Controls> {

  override getReactiveLogic(form?: FormGroup) {
    if (!form) {
      throw new Error(
        'RegistrationFormHandler requires a form instance'
      );
    }

    this.initializeForm(form);

    const emailControl =
      this.getControl('personalInfo.email');

    return this.valueChangesOf(
      'preferences.agreeToTerms'
    ).subscribe((acceptedTerms) => {

      const validators = acceptedTerms
        ? [Validators.required, Validators.email]
        : [Validators.email];

      emailControl.setValidators(validators);
      emailControl.updateValueAndValidity({
        emitEvent: false,
      });
    });
  }
}

Instead of managing subscriptions inside components, business rules are isolated into reusable handlers that can be tested independently.

DTO ↔ Form Mapping

API contracts and form models often evolve independently.

ng-modular-forms provides dedicated mappers for translating between them.

export class PreferencesMapper extends FormMapperBase<
  PreferencesDto,
  PreferencesForm,
  PreferencesForm,
  PreferencesOptions
> {

  override fromModel(dto: PreferencesDto) {
    return {
      receiveEmails: dto.receive_emails,
      theme: dto.theme_name,
    };
  }

  override toRequest(formValue: PreferencesForm, options?: PreferencesOptions) {
    return {
      receive_emails: formValue.receiveEmails,
      theme: formValue.theme,
    };
  }
}

Mapping logic remains centralized and reusable instead of being duplicated throughout components and services.

Large Forms and Multi-Step Workflows

As complexity grows, handlers, mappers, hydration, and serialization can be orchestrated through a single coordinator.

@Component({
  providers: [
    MultiStepFormHandler,
    PersonalInfoFormHandler,
    AccountDetailsFormHandler,
    PreferencesFormHandler,
  ],
})
export class MultiStepFormComponent
  extends FormOrchestrator {

  initialize() {
    this.orchestrate({
      form: registrationForm,

      handlerRegistry: [
        inject(MultiStepFormHandler),
        inject(PersonalInfoFormHandler),
        inject(AccountDetailsFormHandler),
        inject(PreferencesFormHandler),
      ],

      mapperRegistry: {
        preferences: new PreferencesMapper(),
      },
    });

    this.hydrateFromModel(existingUser);
  }
}

The same architecture scales from simple forms to enterprise workflows without moving business logic into Angular components.

Developer Quick Start

Clone and run the examples app:

git clone https://github.com/ronbodnar/ng-modular-forms.git
cd ng-modular-forms
npm install
npm start

Open your browser to http://localhost:4200/docs/examples to see the live interactive examples.

Ecosystem Packages

Package Description Links
@ng-modular-forms/core Form orchestration, reactive behavior handling, API mapping, and state hydration npm, docs
@ng-modular-forms/material Angular Material-based input components npm, docs

Unified Input Control API

All integrated control primitives are built on ControlValueAccessor. This allows you to interchange Native selectors for Material selectors instantly without altering a single line of orchestration logic.

Input TypeNative SelectorMaterial SelectorDescription
Text / Passwordnmf-textnmf-mat-textMasking, text parsing, and standard string inputs.
Lookupnmf-lookupnmf-mat-lookupAutomated synchronous/asynchronous auto-complete engine.
Number / Currencynmf-numbernmf-mat-numberStrictly typed runtime numeric normalization.
Datenmf-datepickernmf-mat-datepickerCalendar utility wrappers.
Timenmf-timepickernmf-mat-timepickerStandard temporal data picking inputs.
Selectnmf-selectnmf-mat-selectOption-driven stream selectors.
Textareanmf-textareanmf-mat-textareaMulti-line textual controls.

Features

  • ControlValueAccessor compatible
  • Fully compatible with Angular Reactive Forms
  • Consistent API across all inputs
  • Built-in validation state + error messaging
  • Label, required indicator, and loading state support
  • Behavior-driven input handling (formatting, parsing, restrictions)

License

MIT