Vicoders Reactive Form
Introduce
- Reactive-Form was built on Angular Reactive Form
- Reactive-Form helps us to build a form with many types of fields faster
Install
- Step 1: Download package form npm
yarn add @vicoders/reactive-form
or
npm install @vicoders/reactive-form
- Step2: Import module (app.module.ts)
import { FormsModule, ReactiveFormsModule } from '@angular/forms';
import { AngularReactiveFormModule, UPLOAD_DIRECTIVE_HEADERS, UPLOAD_DIRECTIVE_API_URL, TINYMCE_CONFIG } from '@vicoders/reactive-form';
@NgModule({
declarations: [AppComponent],
imports: [...ReactiveFormsModule, AngularReactiveFormModule],
providers: [
{
provide: UPLOAD_DIRECTIVE_HEADERS,
useValue: {
Authorization: `Bearer ${token}`
}
},
{
provide: UPLOAD_DIRECTIVE_API_URL,
useValue: 'http://api.reflaunt.local'
},
{
provide: TINYMCE_CONFIG,
useValue: {
apiUpload: '/api/v1/upload',
paramName: 'files',
resultTransformer: result => result.data[0].full_path
}
}
],
bootstrap: [AppComponent]
})
export class AppModule {}
- Step 3: Import style & script (angular.json)
...
"styles": [
...
"node_modules/primeng/resources/themes/nova-light/theme.css",
"node_modules/primeng/resources/primeng.min.css",
"node_modules/primeicons/primeicons.css",
"node_modules/select2/dist/css/select2.min.css",
...
],
"scripts": [
"node_modules/jquery/dist/jquery.js",
"node_modules/select2/dist/js/select2.min.js",
...
],
...
Use
- Declare an array of selections (app.component.ts)
import { InputBase, TextBox } from '@vicoders/reactive-form';
...
public inputs: any;
ngOnInit() {
let inputs: InputBase<any>[] = [
new TextBox({
key: 'textbox',
label: 'TextBox',
classes: ['col-12'],
group_classes: ['col-12'],
group: 1
})
];
this.inputs = inputs;
}
...
...
<reactive-form [(inputs)]="inputs" [onSubmit]="onSubmit" [onInitReactive]="onInitReactive" [onChangeReactive]="onChange" [keysChange]="keysChange" [scrollOnErrror]="true" submitText="Save">
</reactive-form>
...
- Use Reset Form Button (app.component.html)
...
<reactive-form ... [isReset]="true">
...
</reactive-form>
...
- Use CustomSubmit & CustomButton (app.component.html)
...
<reactive-form ... [customSubmit]="customSubmit" customeSubmitText="ok" [customButton]="customButton" customButtonText="ok1">
...
</reactive-form>
...
- Get form value (app.component.ts)
...
onSubmit(form) {
if (form.valid) {
console.log('data', form.value);
}
}
onInitReactive(form) {
form.get('textbox-1').valueChanges.subscribe((val) => {
form.get('textbox-2').setValue(val);
});
}
onChange(form) {
if (form.valid) {
console.log('data', form.value);
}
}
customSubmit = form => {
this.onSubmit(form);
}
customButton = () => {
}
...
Selections
- Each Selection will have its own options, but all of them have default options:
Option |
Type |
Desciption |
Defalut Value |
label |
string |
label of selector (label: 'label' ) |
' ' |
key |
string |
key of selector (key: 'key' ) |
' ' |
value |
any |
value of selector |
null |
classes |
array |
A array of strings, use to add class for selector (classes: ['col-6'] ) |
[ ] |
group |
integer |
Any selectors that have the same group will be in the same group |
null |
group_classes |
array |
Any class of group (group) |
null |
validators |
array (ValidatorFn) |
A array of object (validators: [ { label: VALIDATOR_REQUIRED, validator: Validators.required, message: 'This field is required' }] ) |
[ ] |
TextBox
Option |
Type |
Desciption |
Defalut Value |
type |
string |
'password', 'number', ... |
none |
disabled |
boolean |
|
false |
- Result data Type: String (example:
'This is my textBox'
)
CheckBox
- Result data Type: Boolean (example:
true || false
)
Radio
Option |
Type |
Desciption |
Defalut Value |
options |
object |
example: [{ value: 1, label: 'label' }] |
[ ] |
content |
html |
example: `` |
null |
- Result data Type: Value of Object was choosed:
1
Dropdown
Option |
Type |
Desciption |
Defalut Value |
options |
object |
example: [{ value: 1, label: 'label' }] |
[ ] |
disabled |
boolean |
|
false |
- Result data Type: Object (example:
{ label: 'label', value: 1 }
)
DateTimePicker
Option |
Type |
Desciption |
Defalut Value |
showIcon |
Boolean |
true : display icon, false : undisplay icon |
false |
monthNavigator |
Boolean |
true : display month navigator, false : undisplay month navigator |
false |
yearNavigator |
Boolean |
true : display year navigator, false : undisplay year navigator |
false |
yearRange |
String |
example: '1995:2050' |
'1995:2050' |
showTime |
Boolean |
true : display time, false : undisplay time |
false |
timeOnly |
Boolean |
true : display only time, false : undisplay time |
false |
dateFormat |
String |
example" 'dd/mm/yy' |
'dd/mm/yy' |
disabled |
boolean |
|
false |
minDate |
Date |
new Date('month/date/year') // 11/05/2018 |
null |
maxDate |
Date |
new Date('month/date/year') // 11/10/2018 |
null |
Dropzone
Option |
Type |
Desciption |
Defalut Value |
message |
String |
Description of selector |
' ' |
upload_path |
String |
Name of folder save your file |
'folder' |
acceptedFiles |
String |
type of files accept upload ('image/*' ) |
* |
maxFilesize |
number |
Maximum size file upload |
50 |
maxFiles |
number |
Maximun number of file upload |
0 |
showPreview |
Boolean |
Display preview content of upload |
false |
url |
String |
Url upload file, example: /api/v1/upload |
' ' |
| paramName | String | Name of param upload file | 'file' |
| resultTransformer | Function | Each Api upload will have a different result structure so you must customize it to push on dropzone, (example: resultTransformer: result => result.data[0].full_path
- data: result of api upload))) |
ListCheckBox
Option |
Type |
Desciption |
Defalut Value |
options |
object |
example: [{ value: 1, label: 'label' }] |
[ ] |
disable |
boolean |
|
false |
- Result data Type: Array value of options (example:
['1', '2']
)
PhoneCode
- Result data Type: Object of phone code & phone number
- Example:
{
code: '+84',
value: '123456789',
alpha2Code: 'VN'
}
Select2
Option |
Type |
Desciption |
Defalut Value |
options |
object |
example: [{ value: 1, label: 'label' }] |
[ ] |
tags |
Boolean |
Added a value not in options |
false |
placeholder |
String |
placeholder of select2 |
' ' |
isSelectAll |
Boolean |
show select all & clear All ? |
false |
selectBtnText |
string |
label of select all btn |
'Select All' |
clearBtnText |
Boolean |
label of clear all btn |
'Clear All' |
- Result data Type: Array value of options was selected
- Example:
['1', '2']
- Set default value: Each option want set default, need add
selected: true
for this.
{
value: 1,
label: 'label'
selected: true
}
TexBoxMask
Option |
Type |
Desciption |
Defalut Value |
mask |
Array |
Array of Regex. (example: Mask Date [/\d/, /\d/, '/', /\d/, /\d/, '/', /\d/, /\d/, /\d/, /\d/] ) |
[ ] |
valueWithCharacter |
Boolean |
Get value with Character or none |
false |
placeholder |
String |
Placeholder |
' ' |
guide |
Boolean |
Show mask when enter Charactor or none |
false |
- Result data Type: String with mask or none
- Example:
"07/10/1995"
"07101995"
TinyMce
Option |
Type |
Desciption |
Defalut Value |
height |
String |
Height of selector |
'300' |
plugins |
String |
Name of plugins you want add to selector. (example: 'print,bold,thin...' ) |
' ' |
UploadFile
Option |
Type |
Desciption |
Defalut Value |
id |
number |
Id of input control UploadFile |
1 |
uploadPath |
String |
Name of folder save your file |
' ' |
accept |
String |
type of files accept upload ('png|jpg|jpeg' ) |
* |
allowMaxSize |
number |
Maximum size file upload |
2 |
multiple |
boolean |
true or false |
false |
apiUpload |
String |
Url upload file, example: /api/v1/upload |
' ' |
paramName |
String |
Name of param upload file |
'files' |
resultTransformer |
Function |
Each Api upload will have a different result structure so you must customize it to push on dropzone, (example: resultTransformer: result => result.data[0].full_path - data: result of api upload))) |
|
title |
Html |
|
Upload |
TextArea
Option |
Type |
Desciption |
Defalut Value |
rows |
Number |
Number lines of textarea |
10 |
Province
- 63 provinces & cities of Vietnam
- Result data Type: String (example:
'Thành phố Hà Nội'
)
SelectionByApi
Option |
Type |
Desciption |
Defalut Value |
apiUpload |
String |
Api used to search or filter 'api/v1/admin/users?search=' |
null |
resultTransformer |
Function |
Each Api upload will have a different result structure so you must customize it to push on SelectionByApi, (example: resultTransformer: result => result.data - data: result of api upload))) |
|
fieldName |
Function |
return label by data object |
"" |
multiple |
boolean |
true or false |
false |
lengthToSearch |
Number |
lenght of character start to filter |
3 |
Block
Option |
Type |
Desciption |
Defalut Value |
classes |
array |
A array of strings, use to add class for selector (classes: ['col-6'] ) |
[ ] |
group |
integer |
Any selectors that have the same group will be in the same group |
null |
group_classes |
array |
Any class of group (group) |
|
content |
Text |
`<p><strong>Buon Ngu</strong></p>` |
|
SingleSelect2
SwitchInput
Captcha
Option |
Type |
Desciption |
Defalut Value |
numberOfChar |
Number |
Number character |
4 |
type |
String |
'number' or 'alphabet' or null |
null |
message |
String |
Message of captcha |
'Captcha not found' |
...
<reactive-form [captcha]="'captcha'"></reactive-form>
...
('captcha' is key of Captcha input in Form)
TypeAhead
- search by api with type ahead ng-boostraps
Option |
Type |
Desciption |
Defalut Value |
apiUpload |
String |
Api used to search or filter 'api/v1/admin/users?search=' |
null |
resultTransformer |
Function |
Each Api upload will have a different result structure so you must customize it to push on SelectionByApi, (example: resultTransformer: result => result.data - data: result of api upload))) |
|
keyName |
array |
array of key display on optional |
"" |
fieldName |
Funtion |
return label in input by key |
"" |
id |
number |
|
"" |
searchBy |
string |
|
"" |
TypeAheadWithoutApi
- use like dropdown & singleselect2
RangerSlider
Option |
Type |
Desciption |
Defalut Value |
min |
number |
|
|
max |
number |
|
|
step |
number |
|
|
miLabel |
string |
|
|
id |
number |
|
|
unit |
string |
|
|
Gutenberg
Option |
Type |
Desciption |
Defalut Value |
add css @import "./../node_modules/@vicoders/ng-gutenberg/style/ng-gutenberg.scss"
Update&fill
use support.UpdateInputsValue(inputs, optionsData)
to update options for Dropdown, Radio, ListCheckBox, Select2
use supportUpdateFormValue(input, valuesData)
to fill data for inputs controll
Full Example (app.component.ts):
...
keysChange = ['textbox'];
public optionsData = {
dropdown: [
{ label: 'Ha Noi', value: 1 },
{ label: 'TP - HCM', value: 2 },
{ label: 'Da Nang', value: 3 }],
radio: [
{ label: 'Nam', value: 'nam' },
{ label: 'Nu', value: 'nu' }
],
listcheckbox: [
{ label: 'Football', value: 1 },
{ label: 'Volleyball', value: 2 },
{ label: 'Movies', value: 3 },
{ label: 'Camping', value: 4 }
],
select2: [
{ label: 'Football', value: 1 },
{ label: 'Volleyball', value: 2 },
{ label: 'Movies', value: 3 },
{ label: 'Camping', value: 4 }
],
singleselect2: [
{ label: 'Football', value: 1 },
{ label: 'Volleyball', value: 2 },
{ label: 'Movies', value: 3 },
{ label: 'Camping', value: 4 }
]
};
public valuesData: any = {
dropdown: _.find(this.data.dropdown, item => item.value === 2),
radio: 'nu',
listcheckbox: [2, 3],
select2: [4, 1],
checkbox: true,
datetimepicker: new Date(),
uploadfile: [
'http://www.tensorflownews.com/wp-content/uploads/2018/09/1_8GVucX9yhnL21KCtcyFDRQ.png',
'https://encrypted-tbn0.gstatic.com/images?q=tbn:ANd9GcTxadr9ykSPoaet-5e7-_YZtueYaRJSvggWtEShh2EJyAjAf5-D'
],
phonecode: {
code: '84',
value: '3684523975'
},
textarea: 'Content',
tinymce: '<b>blabla</b>',
textboxmask: '07101995',
singleselect2: _.find(this.data.singleselect2, item => item.value === 2),
selectionbyapi: {
created_at: '2018-12-18T03:53:00.000Z',
email: 'admin@reflaunt.com',
id: 2,
last_login: '2019-03-28T08:35:08.000Z',
status: 0,
updated_at: '2019-03-28T08:35:08.000Z'
}
};
ngOnInit() {
const inputs: InputBase<any>[] = [
new TextBox({
key: 'textbox11111',
label: 'Textbox',
classes: ['col-12'],
group_classes: ['col-12'],
group: 1
}),
new TextBox({
key: 'textbox',
label: 'Textbox',
classes: ['col-12'],
group_classes: ['col-12'],
group: 1
}),
new Dropdown({
key: 'dropdown',
label: 'Dropdown',
classes: ['col-12'],
group_classes: ['col-12'],
group: 1
}),
new Radio({
key: 'radio',
label: 'Radio',
classes: ['col-12'],
group_classes: ['col-12'],
group: 1
}),
new ListCheckBox({
key: 'listcheckbox',
label: 'ListCheckBox',
classes: ['col-12'],
group_classes: ['col-12'],
group: 1
}),
new Select2({
id: 1,
key: 'select2',
label: 'Select2',
classes: ['col-12'],
group_classes: ['col-12'],
group: 1,
isSelectAll: true
}),
new CheckBox({
key: 'checkbox',
label: 'CheckBox',
classes: ['col-12'],
group_classes: ['col-12'],
group: 1
}),
new DateTimePicker({
key: 'datetimepicker',
label: 'DateTimePicker',
classes: ['col-12'],
group_classes: ['col-12'],
group: 1,
monthNavigator: true,
yearNavigator: true,
yearRange: '1990:2030'
}),
new UploadFile({
key: 'uploadfile',
label: 'UploadFile',
classes: ['col-12'],
group_classes: ['col-12'],
group: 1,
accept: 'png|jpg|jpeg',
allowMaxSize: 2,
apiUpload: '/api/v1/upload',
multiple: true,
resultTransformer: result => result.data[0].full_path,
paramName: 'files'
}),
new PhoneCode({
key: 'phonecode',
label: 'PhoneCode',
classes: ['col-12'],
group_classes: ['col-12'],
group: 1
}),
new TextArea({
key: 'textarea',
label: 'TextArea',
classes: ['col-12'],
group_classes: ['col-12'],
group: 1,
placeholder: 'placeholder of textarea'
}),
new TinyMce({
key: 'tinymce',
label: 'TinyMce',
classes: ['col-12'],
group_classes: ['col-12'],
group: 1
}),
new TextBoxMask({
key: 'textboxmask',
label: 'TextBoxMask',
classes: ['col-12'],
group_classes: ['col-12'],
group: 1,
mask: [/\d/, /\d/, '/', /\d/, /\d/, '/', /\d/, /\d/, /\d/, /\d/]
}),
new Province({
key: 'province',
label: 'Province',
classes: ['col-12'],
group_classes: ['col-12'],
group: 1
}),
new SelectionByApi({
key: 'selectionbyapi',
label: 'SelectionByApi',
classes: ['col-12'],
group_classes: ['col-12'],
group: 1,
apiUpload: '/api/v1/admin/users?search=',
resultTransformer: result => result.data,
fieldName: result => result.email,
multiple: false,
lengthToSearch: 1
}),
new SingleSelect2({
key: 'singleselect2',
label: 'Single Select2',
classes: ['col-12'],
group_classes: ['col-12'],
group: 1
})
];
this.inputs = inputs;
this.inputs = support.UpdateInputsValue(inputs, this.data);
this.inputs = support.UpdateFormValue(inputs, this.values);
}
...
Validation
Default Validation
https://angular.io/api/forms/Validators
...
...
new TextBox({
...
validators: [
{
validator: Validators.required,
message: 'This field is required'
},
{
validator: Validators.pattern('[a-zA-Z0-9.-_]{1,}@[a-zA-Z.-]{2,}[.]{1}[a-zA-Z]{2,}'),
message: 'This field must be a email address'
},
{
validator: Validators.minLength(6),
message: 'Minimmum alowed charactes are 6'
},
{
validator: Validators.maxLength(20),
message: 'Maximmum alowed charactes are 20'
}
],
...
}),
...
Custom Validation
https://angular.io/guide/form-validation#custom-validators
Reactive-Form provide us some default validator
- isEqualValidator('input-key')
- isDifferentValidator('input-key')
- isExistValidator('apiUrl', 'your-message') - 'apiUrl' must return { exist: true } if item exist
...
import { DefaultValidators } from 'angular-reactive-form';
...
new TextBox({
...
validators: [
{
validator: DefaultValidators.isEqualValidator('input-key'),
message: 'This field not match with input-key '
},
{
validator: DefaultValidators.isDifferentValidator('input-key'),
message: 'This field must diffrent with input-key'
}
],
asyncValidators: [DefaultValidators.isExistValidator(apiUrl, yourMessage)]
...
}),
...
If you want create a custom validator:
- Step 1: Create new file (example.validators.ts)
...
import { AbstractControl, ValidationErrors, AsyncValidatorFn } from '@angular/forms';
export function isEqualValidator(matchValue: any) {
return (control: AbstractControl): ValidationErrors | null => {
const value = control.value;
if (value) {
const compareValue = control.root.get(matchValue).value;
if (value !== compareValue) {
return { notEqual: { valid: false, value: control.value } };
}
}
return null;
};
}
export function isExistValidator(url, message): AsyncValidatorFn {
return (control: AbstractControl): Promise<ValidationErrors | null> | Observable<ValidationErrors | null> => {
/* Your code */
return new Promise(async (resolve, reject) => {
if (value) {
/* Your code => result */
if (result.exist === true) {
resolve({ isExist: { valid: false, value: control.value, message: message } });
}
resolve(null);
}
resolve(null);
});
};
}
...
- Step 2: Use (app.component.ts)
...
import { isEqualValidator, isExistValidator } from './validators/example';
...
new TextBox({
...
validators: [
{
validator: isEqualValidator('input-key'),
message: 'This field not match with input-key '
}
],
asyncValidators: [isExistValidator('/api/v1/test/check/', 'Email is Exits')]
...
}),
...
uu
Theme No.1 (ThemeID: reactive-form-theme-1)
- angular.js: add style.css:
...
"styles": [
...
"node_modules/@vicoders/reactive-form/style/styles.scss",
],
...
- With Form want use new style: <reactive-form
[themeID]="'reactive-form-theme-1'" >