Gathering detailed insights and metrics for @luistabotelho/angular-signal-forms
Gathering detailed insights and metrics for @luistabotelho/angular-signal-forms
Gathering detailed insights and metrics for @luistabotelho/angular-signal-forms
Gathering detailed insights and metrics for @luistabotelho/angular-signal-forms
npm install @luistabotelho/angular-signal-forms
Typescript
Module System
Node Version
NPM Version
Cumulative downloads
Total Downloads
Last Day
0%
NaN
Compared to previous day
Last Week
0%
NaN
Compared to previous week
Last Month
0%
NaN
Compared to previous month
Last Year
0%
NaN
Compared to previous year
1
2
A simple library to manage forms using signals. Use the provided signalForm
[!NOTE] If you have suggestions on how to improve this documentation, leave your feedback here
[!NOTE] If you want to report a bug or suggest an improvement create an issue here
signalForm()
The signalForm<T>(initialValue, options?)
function is the basis of the library. It takes in a SignalFormDefinition and an optional SignalFormOptions.
This function returns a new instance of SignalForm<T> where T is the generic user data.
Examples:
1form1 = signalForm({ 2 field1: { 3 initialValue: "" 4 } 5}) 6 7// Defining <T> manually adds validation to SignalFormDefinition, garanteing that the resulting form is a valid DataType 8form2 = signalForm<DataType>({ 9 someField: { 10 initialValue: 0 11 } 12})
signalFormGroup()
The signalFormGroup<T>(initialValue, options?)
function works in a similar way as the signalForm() function, except it generates a SignalFormGroup<T>, which is a wrapper around an array of SignalForm<T>, allowing to quickly add and remove a SignalForm from the group, as well as get validation, errors and values from it.
The function accepts the same inputs as signalForm(), and those are passed down to the individual members of the group.
Examples:
1formGroup = signalFormGroup<DataType>({ 2 field1: { 3 initialValue: "" 4 } 5}) 6formGroup2 = signalFormGroup<DataType>({ 7 field1: { 8 initialValue: "" 9 } 10}, { 11 requireTouched: true, 12 defaultState: "success" 13})
.data
A WritableSignal<Array<SignalForm<T>>>
. This signal can be manipulated manually or with help of the other SignalFormGroup methods.
.addItem()
Allows adding a new item to the group. This accepts an optional object of type Partial<T> as an input to pre-fill the form fields. If not passed the SignalForm will be created with the initialValue supplied to the group.
.removeItem()
Allows removing an item from the group by passing it's index in the array.
.value()
Returns a signal with the value as Array<T>
. This is the same as applying signalFormValue() to all members of the group and joining the result in a single array.
.valid()
Returns a signal of boolean representing the validity of all members of the group. This is the same as applying signalFormValid() on all members of the group.
.errors()
Returns a signal with an Array<string>
containing all the errors of the group members in format: "{fieldKey}: {error}". (If two members have the same error the error will appear dupplicated in the array).
T
T
is the generic type that represents the data the form is handling. This is the DataType created by the user, it can be UserModel, CarModel, or anything else.
[!WARNING] Currently signalForm doesn't support deeply nested structures. Adding support for that is under investigation and input is welcomed.
Example:
1interface DataType { 2 field1: string 3 field1Child: string 4 field2: string 5 dateField: string 6}
K
K
is defined as a keyof T
, therefore it represents a property of T
.
We will now refer to K as a field(s).
SignalForm<T>
The instance of SignalForm
generated by signalForm(). The SignalForm
instance has the same fields as T, but gives each field some extra properties as seen below:
1[K in keyof T]: { 2 initialValue: T[K], 3 validators: Array<ValidatorFunction<T, T[K]>>, 4 currentValue: WritableSignal<T[K]>, 5 touched: WritableSignal<boolean>, 6 state: Signal<State>, 7 valid: Signal<boolean> 8}
Is constant and represents the initialValue defined by the user.
Is the array of ValidatorFunction defined by the user.
A WriteableSignal representing the current value of the field. This is the property that should be bound to the input fields.
[(ngModel)]="form.field1.currentValue"
A WriteableSignal to represent weather the field was touched. Touched has to be manually handled and the recomendation is to bind it to the blur event of the input.
(blur)="form.field1.touched.set(true)"
A Signal containing the current input State
A Signal containing a boolean that represents if the field is valid or not based on the validators
SignalFormGroup<T>
SignalFormDefinition<T>
The initial definition accepted by the signalForm() constructor. Each field has two properties: initialValue and validators.
1{ 2 field1: { 3 initialValue: "", 4 validators: [ 5 (val) => !val ? new Error("Required") : null, 6 (val) => val && !RegExp(/^[A-Z]{1}/).test(val) ? new Error("First letter must be upper case") : null, 7 (val) => val && val.length > 10 ? new Error("Must not exceed 10 characters") : null 8 ] 9 } 10}
The initial value of the field.
An array of ValidatorFunction. Keep in mind they are run in sequence, and therefore should be defined in order of priority. Ex.: "Required" will always appear before "First letter must be upper case".
SignalFormOptions
A series of options that optionally can be passed to the signalForm() function. Current options are:
1{ 2 requireTouched: true, 3 defaultState: 'default', 4 errorState: 'error' 5}
If true
requires the input to be touched before displaying the error state.
Default: true
The default state of the input field if all validators pass.
Default: default
The error state of the input field if any of the validators returns an Error.
Default: error
State
The value of the state
property of each field. The State has two properties:
1{ 2 state: string, 3 message: string | null 4}
The current state of the input field. Either defaultState or errorState.
ValidatorFunction<T, K>
A function which takes in two parameters, PropertyValue and FormValue, and returns an Error
or null
.
Example:
1(propertyValue, formValue) => !propertyValue && formValue.otherField.currentValue() != "Some Value" ? new Error("Required") : null
This function will return an error if this fields currentValue is Falsy and otherFields currentValue != "Some Value".
resetSignalForm()
Accepts an instance of SignalForm.
Sets the currentValue of all fields to the initialValue and sets touched to false, essentially returning the form to it's initial state.
signalFormValue()
Accepts an instance of SignalForm
Returns a Signal containing the updated instance of T.
Example: { "field1": "Input 1 value", "field1Child": "", "field2": "Input 2 value", "dateField": "2024-11-27T21:54" }
signalFormValid()
Accepts an instance of SignalForm
Returns a Signal containing a boolean representing if all fields in the form are valid.
signalFormErrors()
Accepts an instance of SignalForm
Returns a Signal containing an array of all errors returned by the SignalForm instance. This is usefull if you want to display all errors to the user at once.
This does not take into consideration the touched property and will return all errors regardless.
signalFormSetTouched()
Accepts an instance of SignalForm
Will set all fields in the form to touched, making their state go into error if they are invalid even if the user didn't touch them.
This can be used if you want todisplay all fields with errors when the user attempts to submit the form, even if the user didn't interact with the field.
Be aware that this is not required if the requireTouched option was set to false.
1import { Component, computed } from '@angular/core'; 2import { signalForm, signalFormValue, signalFormValid, resetSignalForm, signalFormSetTouched, signalFormGroup, signalFormErrors, signalFormGroupErrors, signalFormGroupValid, signalFormGroupValue } from 'signal-forms'; 3import { FormsModule } from '@angular/forms'; 4import { CommonModule } from '@angular/common'; 5 6interface DataType { 7 field1: string 8 field1Child: string 9 field2: string 10 dateField: string 11} 12 13interface TableItem { 14 id: string 15 name: string 16} 17 18@Component({ 19 selector: 'app-root', 20 standalone: true, 21 imports: [FormsModule, CommonModule], 22 templateUrl: './app.component.html', 23 styleUrl: './app.component.css' 24}) 25export class AppComponent { 26 form = signalForm<DataType>({ 27 field1: { 28 initialValue: "", 29 validators: [ 30 (val) => !val ? new Error("Required") : null, 31 (val) => val && !RegExp(/^[A-Z]{1}/).test(val) ? new Error("First letter must be upper case") : null, 32 (val) => val && val.length > 10 ? new Error("Must not exceed 10 characters") : null 33 ] 34 }, 35 field1Child: { 36 initialValue: "", 37 validators: [ 38 (val, form) => !val && form.field1.$currentValue() ? new Error("Required if Field 1 contains a value") : null, 39 ] 40 }, 41 field2: { 42 initialValue: "" 43 }, 44 dateField: { 45 initialValue: new Date().toISOString().slice(0, 16), 46 validators: [ 47 (val) => !val ? new Error("Required") : null, 48 (val) => val.slice(0, 10) < new Date().toISOString().slice(0, 10) ? new Error("Date cannot be in the past") : null 49 ] 50 } 51 }) 52 53 $formValue = signalFormValue(this.form) 54 $formErrors = signalFormErrors(this.form) 55 $formValid = signalFormValid(this.form) 56 57 $tableData = signalFormGroup<TableItem>({ 58 id: { 59 initialValue: '', 60 validators: [ 61 val => !val ? new Error('Required') : null 62 ] 63 }, 64 name: { 65 initialValue: '', 66 validators: undefined 67 } 68 }) 69 70 $tableValid = signalFormGroupValid(this.$tableData) 71 $tableErrors = signalFormGroupErrors(this.$tableData) 72 $tableValue = signalFormGroupValue(this.$tableData) 73 74 $completeValid = computed(() => this.$formValid() && this.$tableValid()) 75 $completeErrors = computed(() => [...this.$formErrors(), ...this.$tableErrors()]) 76 $completeValue = computed(() => ({ 77 ...this.$formValue(), 78 table: [ 79 ...this.$tableValue() 80 ] 81 })) 82 83 resetForm = () => { 84 resetSignalForm(this.form) 85 this.$tableData.$data.set([]) 86 } 87 88 submit() { 89 signalFormSetTouched(this.form) 90 this.$tableData.$data().forEach(tableForm => { 91 signalFormSetTouched(tableForm) 92 }) 93 if (!this.$completeValid()) { 94 return 95 } 96 // submit to server 97 } 98} 99
1<div> 2 <label for="field1">Text Input 1</label> 3 <br> 4 <input 5 id="field1" 6 type="text" 7 (focus)="form.field1.$touched.set(true)" 8 [(ngModel)]="form.field1.$currentValue"> 9 <br> 10 Touched: {{form.field1.$touched()}} 11 <br> 12 State: {{form.field1.$state()}} : {{form.field1.$stateMessage()}} 13</div> 14<br><br> 15<div> 16 <label for="field1Child">Text Input 2 Depends on Text Input 1</label> 17 <br> 18 <input 19 id="field1Child" 20 type="text" 21 (focus)="form.field1Child.$touched.set(true)" 22 [(ngModel)]="form.field1Child.$currentValue"> 23 <br> 24 Touched: {{form.field1Child.$touched()}} 25 <br> 26 State: {{form.field1Child.$state()}} : {{form.field1Child.$stateMessage()}} 27</div> 28<br><br> 29<div> 30 <label for="field2">Text Input with no Validations</label> 31 <br> 32 <input 33 id="field2" 34 type="text" 35 (focus)="form.field2.$touched.set(true)" 36 [(ngModel)]="form.field2.$currentValue"> 37 <br> 38 Touched: {{form.field2.$touched()}} 39 <br> 40 State: {{form.field2.$state()}} : {{form.field2.$stateMessage()}} 41</div> 42<br><br> 43<div> 44 <label for="dateField">Date Input</label> 45 <br> 46 <input 47 id="dateField" 48 type="datetime-local" 49 (focus)="form.dateField.$touched.set(true)" 50 [(ngModel)]="form.dateField.$currentValue" 51 > 52 <br> 53 Touched: {{form.dateField.$touched()}} 54 <br> 55 State: {{form.dateField.$state()}} : {{form.dateField.$stateMessage()}} 56</div> 57<br><br> 58<div> 59 Form Valid: {{$formValid()}} 60 <br> 61 Current Value: {{$formValue() | json}} 62 <br> 63 All Errors: {{$formErrors() | json}} 64</div> 65<br><br> 66<div> 67 <table> 68 <thead> 69 <th>ID</th> 70 <th>Name</th> 71 <th></th> 72 </thead> 73 <tbody> 74 @for (item of $tableData.$data(); track $index) { 75 <tr> 76 <td> 77 <input type="text" [(ngModel)]="item.id.$currentValue"> 78 </td> 79 <td> 80 <input type="text" [(ngModel)]="item.name.$currentValue"> 81 </td> 82 <td> 83 <button (click)="$tableData.removeItem($index)">Delete</button> 84 </td> 85 </tr> 86 } 87 <tr> 88 <td colspan="3"> 89 <button (click)="$tableData.addItem()">Add</button> 90 </td> 91 </tr> 92 </tbody> 93 </table> 94</div> 95<br><br> 96<div> 97 Table Valid: {{$tableValid()}} 98 <br> 99 Table Errors: {{$tableErrors() | json}} 100 <br> 101 Table Value: {{$tableValue() | json}} 102</div> 103<br><br> 104<div> 105 Complete Valid: {{$completeValid()}} 106 <br> 107 Complete Errors: {{$completeErrors() | json}} 108 <br> 109 Complete Value: {{$completeValue() | json}} 110 <br><br> 111 <button (click)="resetForm()">Reset Form</button> 112 <br> 113 <button (click)="submit()">Submit</button> 114 <br> 115 <button (click)="submit()" [disabled]="!$completeValid()">Submit If Valid</button> 116</div>
No vulnerabilities found.
No security vulnerabilities found.