A form library extending Angular's template driven form capabilities.
npm install angular-forms-extension
When using SystemJS don't forget to:
map: 'angular-forms-extension': 'angular-form-extension/bundles/angular-hybrid-forms.umd.min.js'
##Initial setup
In the root app module provide FormsExtensionModule
and use static method forRoot
in order to initialize the FormValidationMessageService
.
this is to make sure there is a singleton service.
@NgModule({
imports: [FormsExtensionModule.forRoot()],
bootstrap: [AppComponent]
})
export class AppModule {
}
In others modules that use the FormsExtensionModule
just import it, without forRoot
method:
@NgModule({
imports: [FormsExtensionModule, FormsModule],
bootstrap: [UserComponent]
})
export class UserModule {
}
Adding a template driven inner form won't work with nested components using ngModelGroup
.
Instead, you could simply place a <form></form>
around your inner group, and angular-form-extensions
will do the wiring between the two for you.
Simply wrap your inner form with <form></form>
@Component({
template: `
<form>
<input [(ngModel)]='name' [name]='name' required>
<address></address>
</form>
`
})
class UserFormComponent {
@Input name: string;
}
@Component({
template: `
<form>
<input [(ngModel)]='city' [name]='city' required>
<input [(ngModel)]='street' [name]='street'>
<input [(ngModel)]='zipcode' [name]='zipcode' [minLength]='5' [maxLength]='5'>
</form>
`
})
class UserFormComponent {
@Input city: string;
@Input street: string;
@Input zipcode: number;
}
Usually, when your form is submitted, it always calls your ngSubmit bound method and you have to manually check if the form is valid, before sending the info to the server.
With this directive, instead, the method is called only if there are no errors.
Moreover, the novalidate attribute is automatically added to your form.
Simply subscribe to the (validSubmit)
event on your form:
<form (validSubmit)="save()">
...
</form>
While a classic form usually has some sort of submit button, there is a variant of an "autosave" form, in which fields are saved individually after changing so long as they're valid.
That is why we've added the (ngModelValidChange)
(to go along with Angular's ngModelChange
).
This event will fire only when the form field has changed, AND it is valid.
Also, in order to not overwhelm the server with a save request on every type, we've added a debounce time.
It is set to 400ms by default, but can be altered using the [ngModelValidChangeDebounce]="...""
input.
Simply subscribe to the (validSubmit)
event on your form:
<form (validSubmit)="save()">
<input [(ngModel)]='name' [name]='name' (ngModelValidChange)='saveName($event)' required>
</form>
Sometimes, you'll want to alert the user when his form has unsaved date (dirty) as they navigate away from the page.
When using Angular, you might want to consider implementing the CanDeactivate interface.
The (unsaved) event will emit true
when the form state has changed and has content that was not saved, and false
when it does not.
When a form is submitted all at once using (validSubmit), it will emit true
when a change was detected, and false
When each individual form field is saved on its own (like when using the (ngModelChangeValid)
event), it will emit true
for a short while based on the debounce time, and back to false
once it is saved.
@Component({template: `
<form (validSubmit) (unsaved)="unsavedChanged($event)">
...
</form>`})
MyFormComponent extends FormCanDeactivate {
...
}
export abstract class FormCanDeactivate {
private unsaved: boolean;
canDeactivate(): boolean {
return !this.unsaved;
}
unsavedChanges(value: boolean) {
this.unsaved = value;
}
}
Wrapping your form controls with fx-field
will add a label for you, and place proper CSS on both your label and form control, allowing you to customize it in common situations - like when it is a required field, or invalid.
It will also apply common practice, like not highlighting invalid fields till they've changed or the form was submitted.
Further more, it adds a human readable error message to the right of the field when it is invalid.
required: '[Label] is required',
minlength: '[Label] must be at least {{requiredLength}} characters long',
maxlength: '[Label] must be no more than {{requiredLength}} characters long',
email: '[Label] must be valid',
@NgModule({
imports: [BrowserModule,
FormsExtensionModule.forRoot({myCustomValidation: '...', minlength: 'hi it`s too long!'})],
declarations: [AppComponent],
bootstrap: [AppComponent]
})
export class AppModule {
}
In case you want the error message to include also the field name, you can put it in your message as placeholder.
for example: {{fieldName}} must be at least 5 characters
fx-field
- The entire form field, including its label and its control.
fx-field--required
- A modifier applied to the form field when it is required.
fx-field--invalid
- A modifier applied to the form field when it is invalid. Only shown when field is dirty or submitted.
fx-field__label
- The label part of the form field.
fx-field__control
- The form control part of the form field.
fx-field__errors
- The errors pane of the form field.
fx-field__error
- A class applied to each individual error.
<form (validSubmit)="save()">
<xf-field label="City">
<input [(ngModel)]='city' [name]='city' required>
</xf-field>
<xf-field label="Street">
<input [(ngModel)]='street' [name]='street'>
</xf-field>
<xf-field label="Zipcode">
<input [(ngModel)]='zipcode' [name]='zipcode' [minLength]='5' [maxLength]='5'>
</xf-field>
<button>Submit</button>
</form>
Common tasks are present as npm scripts:
npm start
to run a live-reload server with the demo appnpm run test
to test in watch mode, ornpm run test:once
to only run oncenpm run build
to build the librarynpm run lint
to lintnpm run clean
to cleannpm install ./relative/path/to/lib
afternpm run build
to test locally in another app
Using npm run release