Introduction

Angular Formly is a dynamic JSON powered form library for Angular that brings unmatched maintainability to your application's forms. It provides a powerful and flexible way to generate forms based on configuration objects rather than templates.

📋 What is Angular Formly?

Angular Formly is a form generation library that allows you to define forms using JSON configuration objects. This approach provides better maintainability, reusability, and flexibility compared to traditional template-driven forms.

Key Benefits

  • Configuration-driven: Define forms using simple JSON objects
  • Highly extensible: Create custom field types and validators
  • Framework agnostic: Core logic separated from UI framework
  • Type-safe: Full TypeScript support with strong typing
  • Performance optimized: OnPush change detection strategy

Installation

Install Angular Formly and choose a UI theme that matches your application's design system.

Core Package

Install Core Package
npm install @ngx-formly/core

UI Themes

Choose one of the available UI themes:

Bootstrap Theme
npm install @ngx-formly/bootstrap
Material Theme
npm install @ngx-formly/material
Ionic Theme
npm install @ngx-formly/ionic
PrimeNG Theme
npm install @ngx-formly/primeng
💡 Pro Tip

You can also use Formly without any UI theme and create your own custom field types from scratch.

Quick Start

Get up and running with Angular Formly in just a few steps.

1. Import Modules

app.module.ts
import { NgModule } from '@angular/core'; import { ReactiveFormsModule } from '@angular/forms'; import { FormlyModule } from '@ngx-formly/core'; import { FormlyBootstrapModule } from '@ngx-formly/bootstrap'; @NgModule({ imports: [ ReactiveFormsModule, FormlyModule.forRoot(), FormlyBootstrapModule ] }) export class AppModule { }

2. Create Component

my-form.component.ts
import { Component } from '@angular/core'; import { FormGroup } from '@angular/forms'; import { FormlyFieldConfig } from '@ngx-formly/core'; @Component({ selector: 'app-my-form', template: ` <form [formGroup]="form" (ngSubmit)="onSubmit()"> <formly-form [form]="form" [fields]="fields" [model]="model"></formly-form> <button type="submit">Submit</button> </form> ` }) export class MyFormComponent { form = new FormGroup({}); model = {}; fields: FormlyFieldConfig[] = [ { key: 'email', type: 'input', templateOptions: { label: 'Email', type: 'email', required: true } } ]; onSubmit() { console.log(this.model); } }

Form Configuration

The heart of Angular Formly is the field configuration object. This object defines how your form fields should behave and appear.

Basic Field Configuration

Basic Example
const field: FormlyFieldConfig = { key: 'firstName', type: 'input', templateOptions: { label: 'First Name', placeholder: 'Enter your first name', required: true } };

Field Groups

Group related fields together for better organization:

Field Group Example
const fieldGroup: FormlyFieldConfig = { fieldGroup: [ { key: 'firstName', type: 'input', templateOptions: { label: 'First Name' } }, { key: 'lastName', type: 'input', templateOptions: { label: 'Last Name' } } ] };
📝 Note

Field groups don't create form controls but help organize your form structure and apply CSS classes.

Field Types

Angular Formly provides several built-in field types. The available types depend on your chosen UI theme.

Common Field Types

Type Description Template Options
input Text input field type, placeholder, maxLength, minLength
textarea Multi-line text input rows, cols, placeholder
select Dropdown selection options, multiple
checkbox Boolean checkbox description
radio Radio button group options
multicheckbox Multiple checkboxes options

Field Type Examples

Various Field Types
const fields: FormlyFieldConfig[] = [ // Text Input { key: 'name', type: 'input', templateOptions: { label: 'Name', placeholder: 'Enter your name' } }, // Select Dropdown { key: 'country', type: 'select', templateOptions: { label: 'Country', options: [ { value: 'us', label: 'United States' }, { value: 'uk', label: 'United Kingdom' } ] } }, // Checkbox { key: 'terms', type: 'checkbox', templateOptions: { label: 'I agree to the terms' } } ];

Template Options

Template options control the appearance and behavior of form fields. These options are passed to the field template.

Common Template Options

Option Type Description
label string Field label text
placeholder string Placeholder text
required boolean Makes field required
disabled boolean Disables the field
readonly boolean Makes field read-only
description string Help text for the field

Validation

Angular Formly provides powerful validation capabilities with both built-in and custom validators.

Built-in Validators

Template Options Validation
const field: FormlyFieldConfig = { key: 'email', type: 'input', templateOptions: { label: 'Email', type: 'email', required: true, minLength: 5, maxLength: 50 } };

Custom Validators

Custom Validator
function emailValidator(control: AbstractControl) { const email = control.value; if (!email) return null; const domain = email.split('@')[1]; const allowedDomains = ['gmail.com', 'yahoo.com']; if (!allowedDomains.includes(domain)) { return { 'invalidDomain': true }; } return null; } const field: FormlyFieldConfig = { key: 'email', type: 'input', templateOptions: { label: 'Email', type: 'email' }, validators: { validation: [emailValidator] } };

Async Validators

Async Validator Example
function asyncEmailValidator(control: AbstractControl) { if (!control.value) return of(null); return timer(1000).pipe( switchMap(() => { // Simulate API call const emailExists = control.value === '[email protected]'; return of(emailExists ? { 'emailTaken': true } : null); }) ); } const field: FormlyFieldConfig = { key: 'email', type: 'input', asyncValidators: { uniqueEmail: { expression: asyncEmailValidator, message: 'This email is already taken' } } };
⚠️ Performance Tip

Async validators should be used sparingly as they can impact form performance. Consider debouncing user input before triggering validation.

Custom Fields

Create custom field types to extend Formly's capabilities and match your specific requirements.

Creating a Custom Field

custom-input.component.ts
import { Component } from '@angular/core'; import { FieldType } from '@ngx-formly/core'; @Component({ selector: 'formly-custom-input', template: ` <div class="custom-field"> <label [for]="id">{{ to.label }}</label> <input [id]="id" [type]="to.type || 'text'" [formControl]="formControl" [formlyAttributes]="field" [placeholder]="to.placeholder" class="form-control"> <div *ngIf="showError" class="error-message"> <formly-validation-message [field]="field"></formly-validation-message> </div> </div> `, styles: [` .custom-field { margin-bottom: 1rem; } .error-message { color: #dc3545; font-size: 0.875rem; margin-top: 0.25rem; } `] }) export class CustomInputComponent extends FieldType {}

Register Custom Field

app.module.ts
import { FormlyModule } from '@ngx-formly/core'; import { CustomInputComponent } from './custom-input.component'; @NgModule({ imports: [ FormlyModule.forRoot({ types: [ { name: 'custom-input', component: CustomInputComponent } ] }) ] }) export class AppModule { }

Using Custom Field

Using Custom Field Type
const field: FormlyFieldConfig = { key: 'customField', type: 'custom-input', templateOptions: { label: 'Custom Input', placeholder: 'Enter value' } };

Expressions

Expressions allow you to create dynamic forms that respond to changes in the form model or other fields.

Hide/Show Fields

Conditional Visibility
const fields: FormlyFieldConfig[] = [ { key: 'hasJob', type: 'checkbox', templateOptions: { label: 'I have a job' } }, { key: 'company', type: 'input', templateOptions: { label: 'Company Name' }, hideExpression: '!model.hasJob' } ];

Dynamic Template Options

Dynamic Properties
const field: FormlyFieldConfig = { key: 'email', type: 'input', templateOptions: { label: 'Email' }, expressionProperties: { 'templateOptions.required': 'model.hasAccount', 'templateOptions.disabled': '!model.canEdit' } };

Complex Expressions

Function-based Expression
const field: FormlyFieldConfig = { key: 'discount', type: 'input', hideExpression: (model, formState, field) => { return model.membershipType !== 'premium' || model.orderTotal < 100; } };
💡 Performance Tip

String expressions are more performant than function expressions as they are compiled once and cached.

Lifecycle Hooks

Lifecycle hooks allow you to execute code at specific points in a field's lifecycle.

Available Hooks

Hook Description When Called
onInit Initialize field After field is created
afterContentInit After content initialization After ngAfterContentInit
afterViewInit After view initialization After ngAfterViewInit
onDestroy Cleanup field Before field is destroyed

Using Lifecycle Hooks

Lifecycle Hook Example
const field: FormlyFieldConfig = { key: 'dynamicField', type: 'select', templateOptions: { label: 'Dynamic Options', options: [] }, hooks: { onInit: (field) => { // Load options dynamically field.templateOptions!.options = [ { value: '1', label: 'Option 1' }, { value: '2', label: 'Option 2' } ]; }, afterViewInit: (field) => { // Field is ready for interaction console.log('Field initialized:', field.key); }, onDestroy: (field) => { // Cleanup subscriptions console.log('Field destroyed:', field.key); } } };

FormlyFieldConfig API

Complete reference for the FormlyFieldConfig interface.

Property Type Required Description
key string Optional The model property name
type string Optional The field type name
templateOptions object Optional Options passed to field template
validators object Optional Synchronous validators
asyncValidators object Optional Asynchronous validators
hideExpression string | function Optional Hide field conditionally
expressionProperties object Optional Dynamic property expressions
fieldGroup FormlyFieldConfig[] Optional Nested field configurations
className string Optional CSS class for field wrapper
hooks object Optional Lifecycle hook functions

FormlyModule Configuration

Configure FormlyModule with custom types, validators, and other settings.

Module Configuration

Complete Module Setup
import { FormlyModule } from '@ngx-formly/core'; @NgModule({ imports: [ FormlyModule.forRoot({ types: [ { name: 'custom-input', component: CustomInputComponent }, { name: 'datepicker', component: DatepickerComponent } ], validators: [ { name: 'strongPassword', validation: strongPasswordValidator } ], validationMessages: [ { name: 'required', message: 'This field is required' }, { name: 'strongPassword', message: 'Password must be strong' } ], extras: { lazyRender: true, resetFieldOnHide: true, immutable: true } }) ] }) export class AppModule { }

Configuration Options

Option Type Description
types ConfigOption[] Register custom field types
validators ConfigOption[] Register custom validators
validationMessages ConfigOption[] Custom validation messages
extras object Additional configuration options
📝 Module Configuration

Use forRoot() in your root module and forChild() in feature modules to properly configure FormlyModule.

Best Practices

Performance Optimization

  • Use OnPush change detection strategy in custom field components
  • Enable lazyRender for large forms with many fields
  • Use string expressions instead of functions when possible
  • Implement trackBy functions for dynamic field arrays

Code Organization

  • Create reusable field configuration factories
  • Group related fields using fieldGroup
  • Extract complex validation logic into separate functions
  • Use TypeScript interfaces for strongly typed models

Accessibility

  • Always provide meaningful labels for form fields
  • Use ARIA attributes in custom field templates
  • Ensure proper keyboard navigation support
  • Provide clear error messages and descriptions
🚀 Performance Tip

For large forms, consider implementing virtual scrolling or pagination to maintain good performance.