Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 4 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -59,6 +59,8 @@ For quick prototyping or simple projects, you can use a CDN:
<link rel="stylesheet" href="https://unpkg.com/formeo@latest/dist/formeo.min.css">
```

This exposes the `window.FormeoEditor` and `window.FormeoRenderer` globals. For production, pin a version (e.g. `formeo@5.13.1`) instead of `@latest` and add an `integrity` hash with `crossorigin="anonymous"` — see [Installation](docs/renderer/renderer.md#installation) in the renderer docs.

## Usage

### Basic Setup
Expand Down Expand Up @@ -171,7 +173,7 @@ editor.whenReady().then(() => {
Formeo can be integrated with popular frontend frameworks:

- **[React Integration Guide](https://github.com/Draggable/formeo/blob/main/docs/react-integration.md)** - Custom hooks, functional components, and Context API patterns
- **[Angular Integration Guide](https://github.com/Draggable/formeo/blob/main/docs/angular-integration.md)** - Services, components, and standalone patterns
- **[Angular Integration Guide](https://github.com/Draggable/formeo/blob/main/docs/angular-integration.md)** - A standalone component, SSR-safe setup, and reading submitted answers

## Theming

Expand Down Expand Up @@ -214,6 +216,7 @@ Comprehensive documentation is available in the [docs](https://github.com/Dragga
- **[Actions](https://github.com/Draggable/formeo/blob/main/docs/options/actions/README.md)** - Action handlers
- **[Editor API](https://github.com/Draggable/formeo/blob/main/docs/editor/README.md)** - Editor methods and properties
- **[TypeScript](https://github.com/Draggable/formeo/blob/main/docs/typescript.md)** - Shipped type definitions and typed events
- **[Using a CSS Framework](https://github.com/Draggable/formeo/blob/main/docs/css-frameworks.md)** - Style rendered forms with Bootstrap or any framework
- **[Build Tools](https://github.com/Draggable/formeo/blob/main/docs/tools/README.md)** - Development and build utilities

## Development
Expand Down
8 changes: 8 additions & 0 deletions docs/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -124,4 +124,12 @@ Remove the rendered form from the page and stop its pagination (the page navigat

Type definitions ship with the package; this page lists what is typed and how to narrow the before-hook details.

## [Using a CSS Framework](css-frameworks.md)

The rendered markup and its classes, adding framework classes with `className`, and editor theming.

## [Migrating from formBuilder](migrating-from-formbuilder.md)

Batch-converting formBuilder form exports to Formeo's `formData` shape with `formBuilder2Formeo`.

## [Build Tools](tools/)
11 changes: 0 additions & 11 deletions docs/angular-integration-example/README.md

This file was deleted.

230 changes: 116 additions & 114 deletions docs/angular-integration.md
Original file line number Diff line number Diff line change
@@ -1,145 +1,147 @@
# Angular + Formeo Integration Guide
# Angular + Formeo Integration

## Why the Demo Doesn't Load Real Angular v20
A standalone Angular component that builds a form with `FormeoEditor`, previews it with `FormeoRenderer`, and
cleans up both on destroy. Verified against `formeo` 5.13.1; Angular 17+ syntax (standalone components,
`afterNextRender`).

The current Angular demo in `src/demo/js/frameworks/angular.js` simulates Angular patterns rather than loading the actual Angular framework for several technical reasons:

### Technical Limitations

1. **Dynamic ES Module Loading Complexity**
- Angular v20 requires complex module resolution
- Dependencies between @angular/core, @angular/common, @angular/platform-browser
- Browser ES module loading limitations

2. **Bundle Size Impact**
- Angular v20 with dependencies is ~200KB+ minified
- Would significantly slow down demo loading
- Not suitable for quick demonstrations

3. **Build Tool Requirements**
- Angular typically requires compilation (TypeScript → JavaScript)
- Template compilation and bundling
- Zone.js and other runtime dependencies

4. **Runtime Environment Mismatch**
- Demo runs in vanilla browser environment
- Angular expects Node.js tooling ecosystem

## Recommended Alternatives

### Option 1: Complete Angular Project Example (Recommended)

Create a separate Angular project that demonstrates real integration:
## Install

```bash
# Create new Angular project
ng new formeo-angular-example --routing --style=scss
cd formeo-angular-example

# Install Formeo
npm install formeo

# Run development server
ng serve
```

**Benefits:**
- Real Angular v20 environment
- Proper TypeScript support
- Full Angular CLI tooling
- Production-ready patterns

### Option 2: Improved CDN Loading (Experimental)

Update the demo to use Angular's standalone CDN bundles:

```javascript
// Load Angular via CDN (Angular v20 standalone)
async function loadAngularFromCDN() {
const scripts = [
'https://unpkg.com/@angular/core@20/bundles/core.umd.js',
'https://unpkg.com/@angular/common@20/bundles/common.umd.js',
'https://unpkg.com/@angular/platform-browser@20/bundles/platform-browser.umd.js'
];

for (const src of scripts) {
await loadScript(src);
Add the stylesheet in `angular.json`:

```json
{
"projects": {
"my-app": {
"architect": {
"build": {
"options": {
"styles": ["node_modules/formeo/dist/formeo.min.css", "src/styles.scss"]
}
}
}
}
}
}
```

**Limitations:**
- Still complex dependency management
- Large download size
- Limited TypeScript support
- Not recommended for production
(Or `@import 'formeo/dist/formeo.min.css';` from a global stylesheet instead, if you'd rather not touch
`angular.json`.)

### Option 3: Web Components Approach

Use Angular Elements to create a web component:
## The component

```typescript
// Create Angular Element
import { createCustomElement } from '@angular/elements';
import { FormBuilderComponent } from './form-builder.component';
import { Component, ElementRef, OnDestroy, ViewChild, afterNextRender } from '@angular/core'
import { FormeoEditor, FormeoRenderer } from 'formeo'

@Component({
selector: 'app-form-builder',
standalone: true,
template: `
<div #editorRef></div>
<button type="button" (click)="preview()">Preview</button>
<div #previewRef></div>
`,
})
export class FormBuilderComponent implements OnDestroy {
@ViewChild('editorRef', { static: true }) editorRef!: ElementRef<HTMLElement>
@ViewChild('previewRef', { static: true }) previewRef!: ElementRef<HTMLElement>

private editor?: FormeoEditor
private renderer?: FormeoRenderer

constructor() {
// afterNextRender only runs in the browser (see "Server-side rendering" below), after the view
// holding editorRef/previewRef has rendered.
afterNextRender(() => {
this.editor = new FormeoEditor({ editorContainer: this.editorRef.nativeElement })
this.renderer = new FormeoRenderer({
renderContainer: this.previewRef.nativeElement,
events: {
onSubmit: ({ event, userData }) => {
event.preventDefault() // formeo doesn't call this for you
console.log('submitted:', userData)
},
},
})
})
}

preview(): void {
this.renderer?.render(this.editor?.formData)
}

const FormBuilderElement = createCustomElement(FormBuilderComponent, { injector });
customElements.define('formeo-angular-builder', FormBuilderElement);
ngOnDestroy(): void {
this.editor?.destroy()
this.renderer?.destroy()
}
}
```

**Benefits:**
- Can be loaded in any environment
- Encapsulated Angular functionality
- Framework-agnostic usage
Notes:

### Option 4: Stackblitz/CodeSandbox Integration
- `editorContainer`/`renderContainer` take an `Element` directly, so the `@ViewChild` refs' `.nativeElement`
work without a selector string.
- `editor.formData` is a getter — it always returns the editor's current form definition, so `preview()` just
reads it and hands it to `renderer.render()`.
- `editor.destroy()` and `renderer.destroy()` both exist and are safe to call more than once; call them in
`ngOnDestroy` so a routed-away component doesn't leak Sortable instances or DOM listeners.

Embed live Angular examples using online IDEs:
## Reading submitted answers

```html
<iframe src="https://stackblitz.com/edit/formeo-angular-v20?embed=1"
width="100%" height="600px"></iframe>
```
The renderer's `onSubmit` event fires on the rendered `<form>`'s native `submit` event and hands you
`{ event, form, userData }`, where `userData` is the form's values keyed by field name (repeated names — checkbox
groups, multi-selects — become arrays). Formeo does not call `event.preventDefault()` itself, so call it yourself
if you're not doing a normal form POST. This fragment replaces the `onSubmit` handler inside the `FormeoRenderer`
constructor in the component above, and assumes an injected `HttpClient` (`private http = inject(HttpClient)`,
alongside `provideHttpClient()` in your app config):

## Current Demo Value
```typescript
events: {
onSubmit: ({ event, userData }) => {
event.preventDefault()
this.http.post('/api/submissions', userData).subscribe()
},
}
```

The current simulated demo is still valuable because it:
## Server-side rendering (SSR)

1. **Shows Integration Patterns**: Demonstrates proper Angular component structure
2. **Provides Copy-Paste Code**: Complete, working examples for real projects
3. **Educational Value**: Teaches Angular + Formeo integration concepts
4. **Performance**: Loads quickly without framework overhead
`FormeoEditor` and `FormeoRenderer` both touch `document`, so only construct them in the browser. `afterNextRender`
(used above) already guarantees this — Angular's server renderer never invokes its callback, so no extra guard is
needed.

## Implementation Recommendation
If you're not using `afterNextRender` (e.g. an older Angular version, or constructing Formeo somewhere other than
a component's injection context), guard the same code with `isPlatformBrowser`. This fragment replaces the
constructor's `afterNextRender(...)` call in the component above with an `ngAfterViewInit` lifecycle hook instead
(so the class also needs `implements AfterViewInit`, and the extra imports shown here):

For your use case, I recommend:
```typescript
import { AfterViewInit, Component, PLATFORM_ID, inject } from '@angular/core'
import { isPlatformBrowser } from '@angular/common'

1. **Keep the current demo** for quick pattern demonstration
2. **Add a complete Angular project** in `/docs/angular-integration-example/`
3. **Update the demo documentation** to clearly explain the simulation
4. **Add links to the full project** for developers who want real integration
export class FormBuilderComponent implements AfterViewInit {
private platformId = inject(PLATFORM_ID)

This approach provides both quick learning (demo) and complete implementation (separate project).
ngAfterViewInit(): void {
if (isPlatformBrowser(this.platformId)) {
this.editor = new FormeoEditor({ editorContainer: this.editorRef.nativeElement })
}
}
}
```

## Migration Path
## TypeScript

If you want to enhance the current demo:
Formeo ships its own type definitions (`dist/formeo.d.ts`, `dist/formeo.d.cts`) — nothing extra to install or
declare. See [TypeScript](typescript.md) for what's typed, including the editor and renderer constructors, their
options, and the `events` callbacks used above.

```javascript
// Enhanced demo with better Angular simulation
export async function loadAngularDemo(container) {
// 1. Show comprehensive setup instructions
// 2. Provide modern Angular v20 code examples
// 3. Demonstrate advanced patterns (signals, standalone components)
// 4. Include service-based architecture examples
// 5. Add TypeScript type definitions
}
```
## The demo's Angular tab

The updated demo now includes:
- Modern Angular v20 patterns
- Standalone components
- Angular signals
- Service-based architecture
- Better TypeScript examples
- Comprehensive integration guide
The demo (`npm start`, then the Angular tab) shows the same construction pattern hand-rolled against plain DOM,
without loading Angular itself — see `src/demo/js/frameworks/angular.js`. It's there to demonstrate the pattern
quickly in the browser, not as a real Angular app; use the component above for that.
3 changes: 2 additions & 1 deletion docs/controls/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,8 @@ const inputElement = {

## [Control Options](https://github.com/Draggable/formeo/tree/main/docs/options/controls)

See [Custom controls](custom-controls.md) for a full editor + renderer example.
See [Custom controls](custom-controls.md) for a full editor + renderer example, or
[Rich Text Editors](rich-text-editors.md) for a worked example wiring in CKEditor 5.

## Control Groups

Expand Down
1 change: 1 addition & 0 deletions docs/controls/custom-controls.md
Original file line number Diff line number Diff line change
Expand Up @@ -208,3 +208,4 @@ would throw on.
- [Custom Attribute Types](custom-attribute-types.md) - Attribute input types for a control's `attrs`
- [Renderer: Custom Elements](../renderer/renderer.md#advanced-topics) - The renderer's `elements` option
- [Control sets](#control-sets) - Add several fields at once
- [Rich Text Editors](rich-text-editors.md) - A full worked example: a CKEditor 5 control
Loading
Loading