angular-reactive-form

- [Vicoders Reactive Form](#vicoders-reactive-form) - [Introduce](#introduce) - [Install](#install) - [Use](#use) - [Selections](#selections) - [TextBox](#textbox) - [CheckBox](#checkbox) - [Radio](#radio) - [Dropdown](#dropdown) - [DateTimePick

Usage no npm install needed!

<script type="module">
  import angularReactiveForm from 'https://cdn.skypack.dev/angular-reactive-form';
</script>

README

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 } 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'
    }
  ],
  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" [onChangeReactive]="onChange" [keysChange]="keysChange" 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);
  }
}

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
  • Result data Type: Date

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)))

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

  • Print [HTML] in Form
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

  • Use like DropDown

SwitchInput

  • result: true or false

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'
  • Use
...
<reactive-form [captcha]="'captcha'"></reactive-form>
...

('captcha' is key of Captcha input in Form)

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

  • Use & Example:
...
...
 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')]

        ...
      }),
...