Skip to content

Commit

Permalink
feat: add API to create basic forms (#24)
Browse files Browse the repository at this point in the history
  • Loading branch information
timdeschryver committed Dec 9, 2023
1 parent 8cf9eef commit d03d875
Show file tree
Hide file tree
Showing 11 changed files with 319 additions and 143 deletions.
7 changes: 6 additions & 1 deletion .devcontainer/devcontainer.json
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,12 @@
}
},
// Add the IDs of extensions you want installed when the container is created.
"extensions": ["Angular.ng-template", "esbenp.prettier-vscode", "dbaeumer.vscode-eslint"]
"extensions": [
"Angular.ng-template",
"esbenp.prettier-vscode",
"dbaeumer.vscode-eslint",
"eamodio.gitlens"
]
}
}
}
5 changes: 4 additions & 1 deletion .eslintrc.json
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,10 @@
{
"files": ["*.ts", "*.tsx"],
"extends": ["plugin:@nx/typescript"],
"rules": {}
"rules": {
"@typescript-eslint/ban-types": "off",
"@typescript-eslint/no-explicit-any": "off"
}
},
{
"files": ["*.js", "*.jsx"],
Expand Down
14 changes: 7 additions & 7 deletions apps/example/src/app/app.component.ts
Original file line number Diff line number Diff line change
@@ -1,15 +1,15 @@
import {Component} from '@angular/core';
import {RouterLink, RouterOutlet} from "@angular/router";
import { Component } from '@angular/core';
import { RouterLink, RouterOutlet } from '@angular/router';

@Component({
selector: 'app-root',
imports: [
RouterLink,
RouterOutlet
],
imports: [RouterLink, RouterOutlet],
template: `
<nav class="mainNav">
<ul>
<li>
<a routerLink="basic-form">Basic form</a>
</li>
<li>
<a routerLink="simple-form">Simple form</a>
</li>
Expand All @@ -18,7 +18,7 @@ import {RouterLink, RouterOutlet} from "@angular/router";
</li>
</ul>
</nav>
<router-outlet/>
<router-outlet />
`,
standalone: true,
})
Expand Down
16 changes: 10 additions & 6 deletions apps/example/src/app/app.routes.ts
Original file line number Diff line number Diff line change
@@ -1,17 +1,21 @@
import {Routes} from "@angular/router";
import { Routes } from '@angular/router';

export const routes: Routes = [
{
path: '',
redirectTo: 'simple-form',
pathMatch: 'full'
redirectTo: 'basic-form',
pathMatch: 'full',
},
{
path: 'basic-form',
loadComponent: () => import('./basic-form/basic-form.component'),
},
{
path: 'simple-form',
loadComponent: () => import('./simple-form.component')
loadComponent: () => import('./simple-form/simple-form.component'),
},
{
path: 'multi-page-form',
loadChildren: () => import('./multi-page-form/multi-page-form.routes')
}
loadChildren: () => import('./multi-page-form/multi-page-form.routes'),
},
];
91 changes: 91 additions & 0 deletions apps/example/src/app/basic-form/basic-form.component.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,91 @@
import { Component, effect, inject } from '@angular/core';
import {
SignalFormBuilder,
SignalInputDebounceDirective,
SignalInputDirective,
SignalInputErrorDirective,
withErrorComponent,
} from '@ng-signal-forms';
import { JsonPipe, NgFor, NgIf } from '@angular/common';
import { FormsModule } from '@angular/forms';
import { CustomErrorComponent } from '../custom-input-error.component';

@Component({
selector: 'app-basic-form',
template: `
<div class="container">
<div>
<div>
<label>Name</label>
<input ngModel [formField]="form.controls.name" />
</div>
<div>
<label>Age</label>
<input type="number" ngModel [formField]="form.controls.age" />
</div>
</div>
<div>
<button (click)="reset()">Reset form</button>
<h3>States</h3>
<pre
>{{
{
state: form.state(),
dirtyState: form.dirtyState(),
touchedState: form.touchedState(),
valid: form.valid()
} | json
}}
</pre>
<h3>Value</h3>
<pre>{{ form.value() | json }}</pre>
<h3>Errors</h3>
<pre>{{ form.errorsArray() | json }}</pre>
</div>
</div>
`,
standalone: true,
imports: [
JsonPipe,
FormsModule,
SignalInputDirective,
SignalInputErrorDirective,
NgIf,
NgFor,
SignalInputDebounceDirective,
],
providers: [withErrorComponent(CustomErrorComponent)],
})
export default class BasicFormComponent {
private sfb = inject(SignalFormBuilder);

form = this.sfb.createFormGroup<{ name: string; age: number | null }>({
name: 'Alice',
age: null,
});

formChanged = effect(() => {
console.log('form changed:', this.form.value());
});

nameChanged = effect(() => {
console.log('name changed:', this.form.controls.name.value());
});

ageChanged = effect(() => {
console.log('age changed:', this.form.controls.age.value());
});

reset() {
this.form.reset();
}

setForm() {
// TODO: allow form values to be set
}
}
Original file line number Diff line number Diff line change
@@ -1,13 +1,6 @@
import {
Component,
inject,
Signal,
signal,
WritableSignal,
} from '@angular/core';
import { Component, inject, Signal } from '@angular/core';
import {
FormField,
FormGroup,
SetValidationState,
SignalFormBuilder,
SignalInputDebounceDirective,
Expand All @@ -19,7 +12,7 @@ import {
} from '@ng-signal-forms';
import { JsonPipe, NgFor, NgIf } from '@angular/common';
import { FormsModule } from '@angular/forms';
import { CustomErrorComponent } from './custom-input-error.component';
import { CustomErrorComponent } from '../custom-input-error.component';

@Component({
selector: 'app-simple-form',
Expand Down Expand Up @@ -158,9 +151,9 @@ export default class SimpleFormComponent {
),
};
}),
todos: this.sfb.createFormGroup<WritableSignal<FormGroup<Todo>[]>>(
todos: this.sfb.createFormGroup<Todo[]>(
() => {
return signal([]);
return [];
},
{
validators: [V.minLength(1)],
Expand All @@ -187,8 +180,10 @@ export default class SimpleFormComponent {
};

addTodo() {
this.form.controls.todos
.controls.update(todos => [...todos, this.createTodo()]);
this.form.controls.todos.controls.update((todos) => [
...todos,
this.createTodo(),
]);
}

reset() {
Expand Down
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
"private": true,
"scripts": {
"ng": "ng",
"start": "nx serve example",
"start": "nx serve example --host 0.0.0.0",
"build": "nx run-many --target=build --all",
"watch": "nx build --watch --configuration development",
"test": "nx test",
Expand Down
31 changes: 19 additions & 12 deletions packages/platform/src/lib/form-builder.ts
Original file line number Diff line number Diff line change
@@ -1,27 +1,34 @@
import {inject, Injectable, Injector, WritableSignal} from "@angular/core";
import {FormField, createFormField, FormFieldOptions, FormFieldOptionsCreator} from "./form-field";
import {FormGroup, createFormGroup,FormGroupOptions} from "./form-group";
import { inject, Injectable, Injector, WritableSignal } from '@angular/core';
import {
FormField,
createFormField,
FormFieldOptions,
FormFieldOptionsCreator,
} from './form-field';
import { FormGroup, createFormGroup, FormGroupOptions } from './form-group';
import { FormGroupCreator } from './models';

@Injectable({
providedIn: 'root'
providedIn: 'root',
})
export class SignalFormBuilder {
private injector = inject(Injector);

public createFormField<Value>(
value: Value | WritableSignal<Value>,
options?: FormFieldOptions | FormFieldOptionsCreator<Value>,
options?: FormFieldOptions | FormFieldOptionsCreator<Value>
): FormField<Value> {
return createFormField(value, options, this.injector);
}

public createFormGroup<
Controls extends | { [p: string]: FormField | FormGroup }
| WritableSignal<any[]>
>(
formGroupCreator: () => Controls,
public createFormGroup<FormFields extends FormGroupCreator>(
formGroupCreator: FormFields | (() => FormFields),
options?: FormGroupOptions
): FormGroup<Controls> {
return createFormGroup(formGroupCreator, options, this.injector);
): FormGroup<FormFields> {
return createFormGroup<FormFields>(
formGroupCreator,
options,
this.injector
);
}
}
Loading

0 comments on commit d03d875

Please sign in to comment.