DEV Community

Diego Liascovich
Diego Liascovich

Posted on

🧩 Building a Dynamic Form Service in Angular from JSON Configuration

By Diego Liascovich

Full-Stack Developer | Angular | Clean Architecture | WebDev


πŸ“Œ Overview

Reactive Forms in Angular are powerful β€” but building large or dynamic forms manually in every component can get messy.

In this tutorial, we’ll create a generic Angular service that:

  • Dynamically generates a FormGroup from a JSON file.
  • Maps string-based validators to Angular’s built-in validators.
  • Exposes helper methods for managing and observing the form.

Useful for multi-step forms, admin dashboards, dynamic form builders, or simply keeping your components cleaner.


🧱 JSON Configuration Example

{
  "name": {
    "value": "",
    "validators": ["required"]
  },
  "email": {
    "value": "",
    "validators": ["required", "email"]
  },
  "age": {
    "value": 0,
    "validators": []
  }
}
Enter fullscreen mode Exit fullscreen mode

Save this file as:

πŸ“ src/assets/form-definitions/form1.json


βš™οΈ The Generic Form Service

// generic-form.service.ts

import { Injectable } from '@angular/core';
import { HttpClient } from '@angular/common/http';
import { FormBuilder, FormGroup, Validators, AbstractControl } from '@angular/forms';
import { BehaviorSubject, Observable, map } from 'rxjs';

@Injectable({
  providedIn: 'root'
})
export class GenericFormService {
  private form!: FormGroup;
  private formValueSubject = new BehaviorSubject<any>({});

  constructor(private fb: FormBuilder, private http: HttpClient) {}

  loadFormConfig(path: string): Observable<FormGroup> {
    return this.http.get<{ [key: string]: any }>(path).pipe(
      map(config => {
        const group: { [key: string]: any } = {};

        for (const key in config) {
          const field = config[key];
          const value = field.value;
          const validators = this.mapValidators(field.validators || []);
          group[key] = [value, validators];
        }

        this.form = this.fb.group(group);
        this.listenToChanges();
        return this.form;
      })
    );
  }

  private mapValidators(validatorNames: string[]): any[] {
    const validatorMap: { [key: string]: any } = {
      required: Validators.required,
      email: Validators.email,
      min: Validators.min(0),
      max: Validators.max(100),
    };

    return validatorNames.map(name => validatorMap[name]).filter(Boolean);
  }

  getForm(): FormGroup {
    return this.form;
  }

  getControl(name: string): AbstractControl {
    return this.form.get(name)!;
  }

  getValues(): any {
    return this.form.value;
  }

  patch(values: any): void {
    this.form.patchValue(values);
  }

  get valueChanges$(): Observable<any> {
    return this.formValueSubject.asObservable();
  }

  private listenToChanges(): void {
    this.form.valueChanges.subscribe(value => {
      this.formValueSubject.next(value);
    });
  }
}
Enter fullscreen mode Exit fullscreen mode

πŸ§ͺ Using the Service in a Component

@Component({
  selector: 'app-dynamic-form',
  templateUrl: './dynamic-form.component.html'
})
export class DynamicFormComponent implements OnInit {
  form!: FormGroup;

  constructor(private formService: GenericFormService) {}

  ngOnInit(): void {
    this.formService
      .loadFormConfig('assets/form-definitions/form1.json')
      .subscribe(form => {
        this.form = form;

        this.formService.valueChanges$.subscribe(values => {
          console.log('Live form values:', values);
        });
      });
  }

  submit() {
    if (this.form.valid) {
      console.log('Submitted:', this.formService.getValues());
    }
  }
}
Enter fullscreen mode Exit fullscreen mode

βœ… Benefits

  • Centralizes form logic and configuration
  • Enables loading forms dynamically at runtime
  • Keeps components clean and testable
  • Supports reactive state via BehaviorSubject

πŸ’¬ Conclusion

This approach is especially useful when:

  • Your forms are complex or deeply nested
  • You want to build a dynamic form builder tool
  • You maintain multiple form versions/configs

Next steps:

  • Add support for FormArray
  • Extend validator parser to support custom rules or parameters
  • Load form structure from API

πŸ™Œ If you found this helpful, follow me for more Angular and full-stack development content!

Top comments (0)