npm package: @flusys/ng-form-builder
Description: Dynamic form builder for FLUSYS Angular applications
Latest version: 4.1.1

# @flusys/ng-form-builder

> Dynamic form builder library for the FLUSYS Angular platform — visual schema editor, form renderer, response viewer, and conditional logic engine.

[![npm version](https://img.shields.io/npm/v/@flusys/ng-form-builder.svg)](https://www.npmjs.com/package/@flusys/ng-form-builder)
[![Angular](https://img.shields.io/badge/Angular-21-red.svg)](https://angular.io)
[![TypeScript](https://img.shields.io/badge/TypeScript-5.x-blue.svg)](https://www.typescriptlang.org)
[![License: MIT](https://img.shields.io/badge/License-MIT-green.svg)](LICENSE)

---

## Table of Contents

- [Overview](#overview)
- [Features](#features)
- [Compatibility](#compatibility)
- [Installation](#installation)
- [Quick Start](#quick-start)
- [Form Schema](#form-schema)
  - [IFormSchema](#iformschema)
  - [IFormSettings](#iformsettings)
  - [ISection](#isection)
  - [IField Types](#ifield-types)
- [Operation Modes](#operation-modes)
  - [Builder Mode](#builder-mode)
  - [Viewer Mode](#viewer-mode)
  - [Result Mode](#result-mode)
- [Services](#services)
  - [FormBuilderApiService](#formbuilderApiservice)
  - [FormBuilderStateService](#formbuilderStateservice)
  - [ConditionalLogicService](#conditionallogicservice)
  - [PdfExportService](#pdfexportservice)
- [Access Control](#access-control)
- [API Endpoints](#api-endpoints)
- [Troubleshooting](#troubleshooting)
- [License](#license)

---

## Overview

`@flusys/ng-form-builder` provides a complete visual form creation and submission system. Users can design forms via a drag-and-drop builder, publish them for submission, and administrators can view collected responses.

All form definitions are stored as JSON schemas in the backend (`nestjs-form-builder`). The Angular library renders the appropriate mode (builder, viewer, result) based on context.

---

## Features

- ✅ Visual drag-and-drop form builder (CDK Drag-Drop)
- ✅ 11 field types (text, number, email, date, select, multi-select, checkbox, radio, textarea, file, rating)
- ✅ Section-based layout
- ✅ Conditional field logic (show/hide fields based on values)
- ✅ Schema versioning (draft / published)
- ✅ Three access levels: PUBLIC, AUTHENTICATED, ACTION_GROUP
- ✅ Form response viewer with export
- ✅ PDF export of form responses (`PdfExportService`)
- ✅ Computed fields (auto-calculated from other fields)

---

## Compatibility

| Package | Version |
|---------|---------|
| Angular | 21+ |
| @flusys/ng-core | 4.x |
| @flusys/ng-shared | 4.x |
| @angular/cdk | 21+ |

---

## Installation

```bash
npm install @flusys/ng-form-builder @flusys/ng-core @flusys/ng-shared @angular/cdk
```

---

## Quick Start

### 1. Add Form Builder Routes

```typescript
// app.routes.ts
import { FORM_BUILDER_ROUTES } from '@flusys/ng-form-builder';

export const routes: Routes = [
  {
    path: 'forms',
    loadChildren: () => FORM_BUILDER_ROUTES,
  },
];
```

### 2. Embed Builder Component

```typescript
import { FormBuilderComponent } from '@flusys/ng-form-builder';

@Component({
  imports: [FormBuilderComponent],
  template: `
    <flusys-form-builder
      [formId]="formId"
      mode="builder"
      (schemaSaved)="onSchemaSaved($event)"
    />
  `,
})
export class FormEditorPageComponent {
  formId = 'my-form-id';

  onSchemaSaved(schema: IFormSchema): void {
    console.log('Schema saved:', schema);
  }
}
```

---

## Form Schema

### IFormSchema

The complete form definition stored as JSON:

```typescript
interface IFormSchema {
  id: string;
  name: string;
  description?: string;
  settings: IFormSettings;
  sections: ISection[];
  version: number;
  isDraft: boolean;
  accessType: 'PUBLIC' | 'AUTHENTICATED' | 'ACTION_GROUP';
  actionGroup?: string;
  createdAt: string;
  updatedAt: string;
}
```

### IFormSettings

```typescript
interface IFormSettings {
  /** Allow multiple submissions per user */
  allowMultipleSubmissions: boolean;

  /** Show progress bar between sections */
  showProgress: boolean;

  /** Message shown after successful submission */
  successMessage: string;

  /** Redirect URL after submission (optional) */
  redirectUrl?: string;

  /** Enable PDF export of responses */
  enablePdfExport: boolean;

  /** Require authentication to submit */
  requireAuth: boolean;

  /** Response deadline */
  expiresAt?: string;
}
```

### ISection

```typescript
interface ISection {
  id: string;
  title: string;
  description?: string;
  fields: IField[];
  /** Conditional visibility rule */
  condition?: ICondition;
}
```

### IField Types

All field types share a common base:

```typescript
interface IFieldBase {
  id: string;
  type: FieldType;
  label: string;
  placeholder?: string;
  required: boolean;
  /** Conditional visibility */
  condition?: ICondition;
  /** Width in grid columns (1-12) */
  colSpan?: number;
}
```

| Field Type | Description | Extra Properties |
|------------|-------------|-----------------|
| `text` | Single-line text input | `minLength`, `maxLength`, `pattern` |
| `textarea` | Multi-line text | `minLength`, `maxLength`, `rows` |
| `number` | Numeric input | `min`, `max`, `step` |
| `email` | Email with validation | — |
| `date` | Date picker | `minDate`, `maxDate` |
| `select` | Single-select dropdown | `options: ISelectOption[]` |
| `multi-select` | Multi-select dropdown | `options: ISelectOption[]` |
| `checkbox` | Boolean checkbox | `labelTrue`, `labelFalse` |
| `radio` | Radio button group | `options: ISelectOption[]` |
| `file` | File upload | `accept`, `maxSizeMb`, `multiple` |
| `rating` | Star rating | `max` (default: 5) |
| `computed` | Auto-calculated | `formula: string` |

```typescript
interface ISelectOption {
  label: string;
  value: string | number;
}

interface ICondition {
  fieldId: string;
  operator: 'equals' | 'not_equals' | 'contains' | 'greater_than' | 'less_than';
  value: unknown;
  /** Combine multiple conditions */
  logic?: 'AND' | 'OR';
  children?: ICondition[];
}
```

---

## Operation Modes

### Builder Mode

Visual form designer with CDK drag-and-drop. Used by administrators to create and edit forms.

```html
<flusys-form-builder
  [formId]="formId"
  mode="builder"
/>
```

Features:
- Left panel: Field type palette
- Center: Drop zones for sections and fields
- Right panel: Selected field properties editor
- Top bar: Save draft / Publish / Preview actions
- Section reordering (drag handle)
- Field reordering within sections (drag handle)
- Conditional logic editor per field

### Viewer Mode

Renders the form for submission. Used by end users to fill out and submit forms.

```html
<flusys-form-builder
  [formId]="formId"
  mode="viewer"
  (submitted)="onFormSubmitted($event)"
/>
```

Features:
- Section-by-section navigation (if multi-section)
- Progress bar
- Client-side validation
- Conditional field show/hide
- File upload fields integrated with `ng-storage`

### Result Mode

View and manage collected form responses. Used by administrators.

```html
<flusys-form-builder
  [formId]="formId"
  mode="result"
/>
```

Features:
- Response list with pagination
- Individual response detail view
- Export as PDF (`PdfExportService`)
- Export all responses as CSV
- Delete response action

---

## Services

### FormBuilderApiService

Typed API client for form management:

```typescript
import { FormBuilderApiService } from '@flusys/ng-form-builder';

@Injectable({ ... })
export class MyService {
  private formApi = inject(FormBuilderApiService);

  getForms(): Observable<IListResponse<IFormSummary>> {
    return this.formApi.getAll({ pageSize: 20 });
  }

  saveSchema(schema: IFormSchema): Observable<ISingleResponse<IFormSchema>> {
    return schema.id
      ? this.formApi.update(schema)
      : this.formApi.insert(schema);
  }

  publishForm(id: string): Observable<IMessageResponse> {
    return this.formApi.publish(id);
  }

  submitResponse(formId: string, data: Record<string, unknown>): Observable<IMessageResponse> {
    return this.formApi.submit({ formId, data });
  }
}
```

### FormBuilderStateService

Component-scoped service (not root-provided) managing the builder's live state:

```typescript
// This service is component-scoped — provide it in the component, not root
@Component({
  providers: [FormBuilderStateService],
})
export class FormBuilderComponent {
  private state = inject(FormBuilderStateService);

  schema = this.state.schema;           // Signal<IFormSchema>
  selectedSection = this.state.selectedSection;   // Signal<ISection | null>
  selectedField = this.state.selectedField;       // Signal<IField | null>
  isDirty = this.state.isDirty;         // Signal<boolean>
}
```

### ConditionalLogicService

Evaluates field conditions against the current form values:

```typescript
import { ConditionalLogicService } from '@flusys/ng-form-builder';

@Component({ ... })
export class FormViewerComponent {
  private condLogic = inject(ConditionalLogicService);

  isFieldVisible(field: IField, formValues: Record<string, unknown>): boolean {
    return this.condLogic.evaluate(field.condition, formValues);
  }
}
```

### PdfExportService

Generates a PDF from a form response:

```typescript
import { PdfExportService } from '@flusys/ng-form-builder';

@Component({ ... })
export class ResponseDetailComponent {
  private pdfExport = inject(PdfExportService);

  downloadPdf(response: IFormResponse): void {
    this.pdfExport.export(response).subscribe(blob => {
      const url = URL.createObjectURL(blob);
      const a = document.createElement('a');
      a.href = url;
      a.download = `response-${response.id}.pdf`;
      a.click();
    });
  }
}
```

---

## Access Control

Forms support three access levels:

| Access Type | Description |
|-------------|-------------|
| `PUBLIC` | Anyone can view and submit (no authentication required) |
| `AUTHENTICATED` | Logged-in users only |
| `ACTION_GROUP` | Only users with specific IAM permission group |

```typescript
// Set access when creating a form schema
const schema: Partial<IFormSchema> = {
  accessType: 'ACTION_GROUP',
  actionGroup: 'survey:respond',
};
```

---

## API Endpoints

| Method | Endpoint | Description |
|--------|----------|-------------|
| POST | `/form-builder/get-all` | List forms |
| POST | `/form-builder/get/:id` | Get form schema |
| POST | `/form-builder/insert` | Create form |
| POST | `/form-builder/update` | Update form |
| POST | `/form-builder/delete` | Delete form |
| POST | `/form-builder/publish/:id` | Publish form (draft → published) |
| POST | `/form-builder/submit` | Submit form response |
| POST | `/form-builder/response/get-all` | List responses for form |
| POST | `/form-builder/response/get/:id` | Get single response |
| POST | `/form-builder/response/delete` | Delete response |
| POST | `/form-builder/response/export-pdf` | Export response as PDF |

---

## Troubleshooting

**Drag-and-drop doesn't work**

Ensure `@angular/cdk` is installed and `DragDropModule` is imported. CDK requires a SCSS import in `angular.json`:

```json
"styles": [
  "node_modules/@angular/cdk/overlay-prebuilt.css"
]
```

**Conditional logic not hiding fields**

Check that `field.condition.fieldId` matches the exact `field.id` of the controlling field. IDs are UUIDs — ensure they're stable across saves.

**File upload fields failing**

File fields require `ng-storage` providers:

```typescript
// app.config.ts
import { provideStorageProviders } from '@flusys/ng-storage';
providers: [...provideStorageProviders()]
```

**`FormBuilderStateService` conflicts between two form instances**

`FormBuilderStateService` is component-scoped. If two `FormBuilderComponent` instances appear on the same page, each provides its own instance automatically.

**PDF export is blank**

Ensure the backend has `wkhtmltopdf` or `puppeteer` configured for PDF generation in `nestjs-form-builder`.

---

## License

MIT © FLUSYS