{"_id":"@dennysjmarquez/ngx-nested-forms","_rev":"2-fa3f5d9b0b042149010ee1e22ae05310","name":"@dennysjmarquez/ngx-nested-forms","dist-tags":{"latest":"1.0.1"},"versions":{"1.0.0":{"name":"@dennysjmarquez/ngx-nested-forms","version":"1.0.0","keywords":["angular","forms","nested-forms","reactive-forms","template-driven-forms","form-management","angular-forms","ngx","centralized-forms","multi-step-forms","wizard-forms"],"author":{"url":"https://dennysjmarquez.dev","name":"Dennys Jose Marquez Reyes","email":"dennysjmarquez@gmail.com"},"license":"MIT","_id":"@dennysjmarquez/ngx-nested-forms@1.0.0","maintainers":[{"name":"dennysjmarquez","email":"dennysjmarquez@gmail.com"}],"homepage":"https://github.com/dennysjmarquez/ngx-nested-forms#readme","bugs":{"url":"https://github.com/dennysjmarquez/ngx-nested-forms/issues"},"dist":{"shasum":"fc26551a298fac71186c478b44d94ba3a4d1a050","tarball":"https://registry.npmjs.org/@dennysjmarquez/ngx-nested-forms/-/ngx-nested-forms-1.0.0.tgz","fileCount":21,"integrity":"sha512-30M7pag5mkWEiWjUJ6TttBgX2ToBjNTV7YpnkjFqMlTjLrSMbGak1Pq6XlrSUzqIxVx9rilYEU8rv/zQ6GZY6A==","signatures":[{"sig":"MEQCIHXz5VE8Ll08dd22GEZhG8W7gaftyZcr20M/8P6mSScZAiAkFn+0P46fXB9JV6EqeA435UJGPT12m/mOi1OKY5Y5/A==","keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U"}],"unpackedSize":149928},"main":"bundles/dennysjmarquez-ngx-nested-forms.umd.js","es2015":"fesm2015/dennysjmarquez-ngx-nested-forms.js","es2020":"fesm2020/dennysjmarquez-ngx-nested-forms.mjs","module":"fesm2015/dennysjmarquez-ngx-nested-forms.mjs","esm2015":"esm2015/dennysjmarquez-ngx-nested-forms.js","esm2020":"esm2020/dennysjmarquez-ngx-nested-forms.mjs","exports":{".":{"node":"./fesm2015/dennysjmarquez-ngx-nested-forms.mjs","types":"./dennysjmarquez-ngx-nested-forms.d.ts","es2015":"./fesm2015/dennysjmarquez-ngx-nested-forms.mjs","es2020":"./fesm2020/dennysjmarquez-ngx-nested-forms.mjs","default":"./fesm2020/dennysjmarquez-ngx-nested-forms.mjs","esm2020":"./esm2020/dennysjmarquez-ngx-nested-forms.mjs"},"./package.json":{"default":"./package.json"}},"gitHead":"8d42d9318b25837d5ca9e500db5ab230c43c4d61","typings":"dennysjmarquez-ngx-nested-forms.d.ts","_npmUser":{"name":"dennysjmarquez","email":"dennysjmarquez@gmail.com"},"fesm2015":"fesm2015/dennysjmarquez-ngx-nested-forms.mjs","fesm2020":"fesm2020/dennysjmarquez-ngx-nested-forms.mjs","repository":{"url":"git+https://github.com/dennysjmarquez/ngx-nested-forms.git","type":"git"},"_npmVersion":"8.19.4","description":"A powerful Angular service for managing nested forms across multiple components with centralized state management","directories":{},"sideEffects":false,"_nodeVersion":"16.20.2","dependencies":{"tslib":"^2.3.0"},"publishConfig":{"access":"public"},"_hasShrinkwrap":false,"peerDependencies":{"rxjs":"^6.5.0 || ^7.0.0","@angular/core":"^13.0.0 || ^14.0.0 || ^15.0.0 || ^16.0.0 || ^17.0.0 || ^18.0.0","@angular/forms":"^13.0.0 || ^14.0.0 || ^15.0.0 || ^16.0.0 || ^17.0.0 || ^18.0.0","@angular/common":"^13.0.0 || ^14.0.0 || ^15.0.0 || ^16.0.0 || ^17.0.0 || ^18.0.0"},"_npmOperationalInternal":{"tmp":"tmp/ngx-nested-forms_1.0.0_1759705909937_0.0840620018317404","host":"s3://npm-registry-packages-npm-production"}},"1.0.1":{"name":"@dennysjmarquez/ngx-nested-forms","version":"1.0.1","description":"A powerful Angular service for managing nested forms across multiple components with centralized state management","keywords":["angular","forms","nested-forms","reactive-forms","template-driven-forms","form-management","angular-forms","ngx","centralized-forms","multi-step-forms","wizard-forms"],"author":{"name":"Dennys Jose Marquez Reyes","email":"dennysjmarquez@gmail.com","url":"https://dennysjmarquez.dev"},"license":"MIT","repository":{"type":"git","url":"git+https://github.com/dennysjmarquez/ngx-nested-forms.git"},"bugs":{"url":"https://github.com/dennysjmarquez/ngx-nested-forms/issues"},"homepage":"https://github.com/dennysjmarquez/ngx-nested-forms#readme","peerDependencies":{"@angular/common":"^13.0.0 || ^14.0.0 || ^15.0.0 || ^16.0.0 || ^17.0.0 || ^18.0.0","@angular/core":"^13.0.0 || ^14.0.0 || ^15.0.0 || ^16.0.0 || ^17.0.0 || ^18.0.0","@angular/forms":"^13.0.0 || ^14.0.0 || ^15.0.0 || ^16.0.0 || ^17.0.0 || ^18.0.0","rxjs":"^6.5.0 || ^7.0.0"},"publishConfig":{"access":"public"},"main":"bundles/dennysjmarquez-ngx-nested-forms.umd.js","module":"fesm2015/dennysjmarquez-ngx-nested-forms.mjs","es2015":"fesm2015/dennysjmarquez-ngx-nested-forms.js","esm2015":"esm2015/dennysjmarquez-ngx-nested-forms.js","fesm2015":"fesm2015/dennysjmarquez-ngx-nested-forms.mjs","typings":"dennysjmarquez-ngx-nested-forms.d.ts","sideEffects":false,"es2020":"fesm2020/dennysjmarquez-ngx-nested-forms.mjs","esm2020":"esm2020/dennysjmarquez-ngx-nested-forms.mjs","fesm2020":"fesm2020/dennysjmarquez-ngx-nested-forms.mjs","exports":{"./package.json":{"default":"./package.json"},".":{"types":"./dennysjmarquez-ngx-nested-forms.d.ts","esm2020":"./esm2020/dennysjmarquez-ngx-nested-forms.mjs","es2020":"./fesm2020/dennysjmarquez-ngx-nested-forms.mjs","es2015":"./fesm2015/dennysjmarquez-ngx-nested-forms.mjs","node":"./fesm2015/dennysjmarquez-ngx-nested-forms.mjs","default":"./fesm2020/dennysjmarquez-ngx-nested-forms.mjs"}},"dependencies":{"tslib":"^2.3.0"},"gitHead":"157d66ecae2406807e117510241671afd7312605","_id":"@dennysjmarquez/ngx-nested-forms@1.0.1","_nodeVersion":"16.20.2","_npmVersion":"8.19.4","dist":{"integrity":"sha512-SugOdvVy0364XTZVY0a52tk8492dJbgyPS1GHuyeiPPoZ2FTe5p/M5767vqakciwAwjWHjo9U6VI1TIUrEFutA==","shasum":"4c1a8c35ca3bd399b71632b9a7b367887332efe1","tarball":"https://registry.npmjs.org/@dennysjmarquez/ngx-nested-forms/-/ngx-nested-forms-1.0.1.tgz","fileCount":21,"unpackedSize":161258,"signatures":[{"keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U","sig":"MEQCIHTxB5oeGCYpeM3+ctzWsdIIONi8RsAbBmbmbE28s86xAiAo/MaPfbYdnuP3O/KEIV/vsaLI+77PNi8++eQG6kB6Xw=="}]},"_npmUser":{"name":"dennysjmarquez","email":"dennysjmarquez@gmail.com"},"directories":{},"maintainers":[{"name":"dennysjmarquez","email":"dennysjmarquez@gmail.com"}],"_npmOperationalInternal":{"host":"s3://npm-registry-packages-npm-production","tmp":"tmp/ngx-nested-forms_1.0.1_1759706488639_0.7783477794892621"},"_hasShrinkwrap":false}},"time":{"created":"2025-10-05T23:11:49.827Z","modified":"2025-10-05T23:21:29.002Z","1.0.0":"2025-10-05T23:11:50.167Z","1.0.1":"2025-10-05T23:21:28.845Z"},"bugs":{"url":"https://github.com/dennysjmarquez/ngx-nested-forms/issues"},"author":{"name":"Dennys Jose Marquez Reyes","email":"dennysjmarquez@gmail.com","url":"https://dennysjmarquez.dev"},"license":"MIT","homepage":"https://github.com/dennysjmarquez/ngx-nested-forms#readme","keywords":["angular","forms","nested-forms","reactive-forms","template-driven-forms","form-management","angular-forms","ngx","centralized-forms","multi-step-forms","wizard-forms"],"repository":{"type":"git","url":"git+https://github.com/dennysjmarquez/ngx-nested-forms.git"},"description":"A powerful Angular service for managing nested forms across multiple components with centralized state management","maintainers":[{"name":"dennysjmarquez","email":"dennysjmarquez@gmail.com"}],"readme":"# @dennysjmarquez/ngx-nested-forms\n\n[![npm version](https://badge.fury.io/js/%40dennysjmarquez%2Fngx-nested-forms.svg)](https://www.npmjs.com/package/@dennysjmarquez/ngx-nested-forms)\n[![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](https://opensource.org/licenses/MIT)\n\n> A powerful Angular service for managing nested forms across multiple components with centralized state management.\n\n## 🚀 Features\n\n- ✅ **Centralized Form Management** - Single source of truth for complex nested forms\n- ✅ **Event System** - Observable-based events with history tracking\n- ✅ **Dynamic Ordering** - Control form element insertion order with `insertAtIndex`\n- ✅ **Conditional Disabling** - Disable all controls except specified ones\n- ✅ **Deep Access** - Access nested controls at any depth level\n- ✅ **No ControlValueAccessor Required** - Simpler than traditional nested form solutions\n- ✅ **TypeScript Support** - Full type safety and IntelliSense\n- ✅ **Hybrid Forms** - Works with both Template-driven and Reactive Forms\n\n## 📦 Installation\n\n```bash\nnpm install @dennysjmarquez/ngx-nested-forms\n```\n\n## 🎯 Problem It Solves\n\nWhen building complex Angular forms with multiple nested components (parent, children, grandchildren), it becomes challenging to:\n\n- Centralize form validation\n- Access data from all nested components\n- Maintain form state across dynamic components\n- Control the order of dynamically added form controls\n- Validate the entire form before submission\n\n**This library solves all these problems with a simple, elegant API.**\n\n## 📖 Basic Usage\n\n### 1. Import the Service\n\nThe service is provided in root by default, but **you should provide it at the component level** to avoid state sharing between different screens:\n\n```typescript\nimport { Component } from '@angular/core';\nimport { FormService } from '@dennysjmarquez/ngx-nested-forms';\n\n@Component({\n  selector: 'app-main-form',\n  templateUrl: './main-form.component.html',\n  providers: [FormService] // ⚠️ Important: Provide at component level\n})\nexport class MainFormComponent {\n  constructor(private formService: FormService) {}\n}\n```\n\n### 2. Register Root Form (Parent Component)\n\n```typescript\nimport { Component, ViewChild, AfterViewInit } from '@angular/core';\nimport { NgForm } from '@angular/forms';\nimport { FormService } from '@dennysjmarquez/ngx-nested-forms';\n\n@Component({\n  selector: 'app-main-form',\n  template: `\n    <form #f=\"ngForm\">\n      <app-personal-info></app-personal-info>\n      <app-address></app-address>\n      <button (click)=\"submit()\">Submit</button>\n    </form>\n  `,\n  providers: [FormService]\n})\nexport class MainFormComponent implements AfterViewInit {\n  @ViewChild('f') form!: NgForm;\n  \n  constructor(private formService: FormService) {}\n  \n  ngAfterViewInit() {\n    // Register the root form\n    this.formService.registerRootForms('mainForm', this.form);\n  }\n  \n  submit() {\n    const form = this.formService.getForm();\n    \n    // Validate entire form\n    form.markAllAsTouched();\n    if (form.invalid) {\n      alert('Form is invalid!');\n      return;\n    }\n    \n    // Get all values\n    const formData = form.get('mainForm')?.getRawValue();\n    console.log('Complete form data:', formData);\n    \n    // Send to backend\n    this.api.save(formData).subscribe();\n  }\n}\n```\n\n### 3. Register Child Forms\n\n```typescript\nimport { Component, ViewChild, AfterViewInit, OnDestroy } from '@angular/core';\nimport { NgForm } from '@angular/forms';\nimport { FormService } from '@dennysjmarquez/ngx-nested-forms';\nimport { Subject, Subscription } from 'rxjs';\nimport { takeUntil } from 'rxjs/operators';\n\n@Component({\n  selector: 'app-personal-info',\n  template: `\n    <form #f=\"ngForm\">\n      <input name=\"firstName\" ngModel placeholder=\"First Name\" required>\n      <input name=\"lastName\" ngModel placeholder=\"Last Name\" required>\n      <input name=\"age\" ngModel type=\"number\" placeholder=\"Age\">\n    </form>\n  `\n})\nexport class PersonalInfoComponent implements AfterViewInit, OnDestroy {\n  @ViewChild('f') form!: NgForm;\n  private formEventSubscription!: Subscription;\n  private destroy$ = new Subject<void>();\n  \n  constructor(private formService: FormService) {}\n  \n  ngAfterViewInit() {\n    // Wait for parent form to be registered\n    this.formEventSubscription = this.formService\n      .getFormEventObservable()\n      .pipe(takeUntil(this.destroy$))\n      .subscribe((event) => {\n        if (event.type === 'form' && event.path === 'mainForm') {\n          // Register this child form\n          this.formService.registerFormElement(\n            'mainForm',\n            'personalInfo',\n            this.form.form\n          );\n          \n          this.formEventSubscription.unsubscribe();\n        }\n      });\n  }\n  \n  ngOnDestroy() {\n    this.destroy$.next();\n    this.destroy$.complete();\n  }\n}\n```\n\n### 4. Deeply Nested Components\n\n```typescript\n@Component({\n  selector: 'app-address-details',\n  template: `\n    <form #f=\"ngForm\">\n      <input name=\"street\" ngModel placeholder=\"Street\">\n      <input name=\"city\" ngModel placeholder=\"City\">\n      <input name=\"zipCode\" ngModel placeholder=\"Zip Code\">\n    </form>\n  `\n})\nexport class AddressDetailsComponent implements AfterViewInit, OnDestroy {\n  @ViewChild('f') form!: NgForm;\n  private destroy$ = new Subject<void>();\n  \n  constructor(private formService: FormService) {}\n  \n  ngAfterViewInit() {\n    this.formService\n      .getFormEventObservable()\n      .pipe(takeUntil(this.destroy$))\n      .subscribe((event) => {\n        // Wait for parent address form\n        if (event.type === 'formElement' && event.path === 'mainForm.address') {\n          // Register as nested child\n          this.formService.registerFormElement(\n            ['mainForm', 'address'],\n            'details',\n            this.form.form\n          );\n        }\n      });\n  }\n  \n  ngOnDestroy() {\n    this.destroy$.next();\n    this.destroy$.complete();\n  }\n}\n```\n\n## 🔥 Advanced Features\n\n### 1. Control Insertion Order with `insertAtIndex`\n\nUseful when components can be destroyed and recreated dynamically, but you need to maintain a specific order:\n\n```typescript\nthis.formService.registerFormElement(\n  ['mainForm', 'tabs'],\n  'tab1',\n  this.form.form,\n  { insertAtIndex: 0, overwrite: true }\n);\n```\n\n### 2. Optimize with Event History\n\nAvoid unnecessary subscriptions by checking if a form is already registered:\n\n```typescript\nngAfterViewInit() {\n  const eventHistory = this.formService.getFormEventHistory();\n  const isParentRegistered = eventHistory.find(\n    event => event.type === 'form' && event.path === 'mainForm'\n  );\n  \n  if (isParentRegistered) {\n    this.registerForm();\n  } else {\n    this.formService.getFormEventObservable()\n      .subscribe(event => {\n        if (event.type === 'form' && event.path === 'mainForm') {\n          this.registerForm();\n        }\n      });\n  }\n}\n```\n\n### 3. Access Nested Controls\n\n```typescript\n// Get a specific control value\nconst firstName = this.formService.getControl('mainForm.personalInfo.firstName');\nconsole.log(firstName?.value);\n\n// Or use array notation\nconst city = this.formService.getControl(['mainForm', 'address', 'details', 'city']);\nconsole.log(city?.value);\n\n// Check if user has filled tasks before allowing change\nconst tasks = this.formService.getControl(['mainForm', 'tasks'])?.value ?? [];\nif (tasks.length > 0) {\n  // Show confirmation dialog\n}\n```\n\n### 4. Disable All Except Specific Fields\n\nPerfect for \"read-only\" modes where only certain fields can be edited:\n\n```typescript\n// Disable all fields except 'status' and 'comments'\nthis.formService.disableAllExcept(\n  'mainForm.personalInfo',\n  ['status', 'comments']\n);\n```\n\n### 5. Remove Form Elements on Destroy\n\nClean up when components are destroyed:\n\n```typescript\nngOnDestroy() {\n  const removed = this.formService.removeFormElement([\n    'mainForm',\n    'address',\n    'details'\n  ]);\n  console.log('Form element removed:', removed);\n  \n  this.destroy$.next();\n  this.destroy$.complete();\n}\n```\n\n### 6. Building Request Payload\n\n```typescript\nsubmit() {\n  const form = this.formService.getForm();\n  \n  // Validate\n  form.markAllAsTouched();\n  if (form.invalid) {\n    this.showValidationErrors();\n    return;\n  }\n  \n  // Get complete form structure\n  const mainForm = form.get('mainForm') as FormGroup;\n  const formData = mainForm.getRawValue();\n  \n  // Extract nested data\n  const { personalInfo, address, preferences } = formData;\n  const { details } = address;\n  \n  // Map to backend model\n  const payload = {\n    userId: this.userId,\n    firstName: personalInfo.firstName,\n    lastName: personalInfo.lastName,\n    age: personalInfo.age,\n    address: {\n      street: details.street,\n      city: details.city,\n      zipCode: details.zipCode\n    },\n    preferences: preferences?.list ?? [] // From FormArray\n  };\n  \n  // Send to API\n  this.apiService.save(payload).subscribe(\n    response => console.log('Saved!', response),\n    error => console.error('Error:', error)\n  );\n}\n```\n\n## 📚 API Reference\n\n### Methods\n\n#### `registerRootForms(name: string, formGroup: FormGroup): void`\nRegister the main/root form.\n\n**Parameters:**\n- `name`: Identifier for the form\n- `formGroup`: FormGroup or NgForm instance\n\n---\n\n#### `registerFormElement(path, controlName, control, options?): FormEvent | null`\nRegister a nested form element.\n\n**Parameters:**\n- `path`: Path to parent form (string or array)\n- `controlName`: Name of the control to register\n- `control`: FormControl, FormGroup, or AbstractControl instance\n- `options`: Optional configuration\n  - `overwrite`: boolean - Replace existing control (default: false)\n  - `insertAtIndex`: number - Insert at specific position\n\n**Returns:** FormEvent object or null if parent not found\n\n---\n\n#### `removeFormElement(path: string | string[]): boolean`\nRemove a form element at the specified path.\n\n**Returns:** true if removed, false otherwise\n\n---\n\n#### `getControl(path: string | string[]): AbstractControl | null`\nGet a control at any nested level.\n\n**Parameters:**\n- `path`: Path to control ('form.subform.control' or ['form', 'subform', 'control'])\n\n---\n\n#### `getForm(): FormGroup`\nGet the main FormGroup with all nested forms.\n\n---\n\n#### `getFormEventObservable(): Observable<FormEvent>`\nGet observable that emits when forms/controls are registered.\n\n---\n\n#### `getFormEventHistory(): FormEvent[]`\nGet array of all registration events (useful for optimization).\n\n---\n\n#### `disableAllExcept(formPath: string, exceptions: string[]): void`\nDisable all controls in a form except specified ones.\n\n**Parameters:**\n- `formPath`: Path to the form\n- `exceptions`: Array of control names to keep enabled\n\n## 🎨 Use Cases\n\n### ✅ Multi-step Wizards\nPerfect for forms split across multiple steps/pages where you need centralized validation.\n\n### ✅ Dynamic Tab Forms\nWhen tabs can be added/removed dynamically and you need to maintain order and validation.\n\n### ✅ Complex Enterprise Forms\nLarge forms with dozens of sections distributed across multiple components.\n\n### ✅ Conditional Form Sections\nForms where sections appear/disappear based on user selections.\n\n### ✅ Lazy Loaded Form Modules\nWhen form sections are loaded lazily but need to integrate into a main form.\n\n## 🆚 Comparison with Other Solutions\n\n| Feature | ngx-nested-forms | ngx-sub-form | Manual @Input/@Output |\n|---------|------------------|--------------|----------------------|\n| No ControlValueAccessor needed | ✅ | ❌ | ✅ |\n| Centralized validation | ✅ | ⚠️ Partial | ❌ |\n| Event system | ✅ | ❌ | ⚠️ Manual |\n| Control insertion order | ✅ | ❌ | ❌ |\n| Event history optimization | ✅ | ❌ | ❌ |\n| Deep nested access | ✅ | ⚠️ Limited | ❌ |\n| Conditional disabling | ✅ | ❌ | ⚠️ Manual |\n| Learning curve | Low | Medium | Low |\n\n## 🤝 Contributing\n\nContributions are welcome! Please feel free to submit a Pull Request.\n\n## 📝 License\n\nMIT License - feel free to use in personal and commercial projects.\n\n## 👤 Author\n\n**Dennys Jose Marquez Reyes**\n- GitHub: [@dennysjmarquez](https://github.com/dennysjmarquez)\n- Email: dennysjmarquez@gmail.com\n- Website: [dennysjmarquez.dev](https://dennysjmarquez.dev/)\n\n## 🙏 Support\n\nIf this library helped you, please give it a ⭐️ on [GitHub](https://github.com/dennysjmarquez/angular-nested-forms-service)!\n\n---\n\nMade with ❤️ for the Angular community\n","readmeFilename":"README.md"}