12th Apr

Angular Formly

Tuesday, April 12, 2016 - 10:09
0

This article is written with expectations of the reader to have basic understanding of the following technologies: html, javascript, Bower, Angular JS

This article aims to supplement angular-formly documentation and highlight important or interesting cases. Angular-formly documentation is located here: http://angular-formly.com

Angular Formly is a module of Angular framework designed to make form construction simpler. The code of a form will be moved from html context and will be placed into a well structured javascript object in JSON format. It is easier to maintain and handle, the possibility of defining own field templates also improves code reusability. The adequate degree of restrictions prevent the code to become cluttered, yet it remains highly customizable.

Angular Formly has two dependencies, Angular JS Framework and api-check library thus the .js files of these components must precede the .js of Angular Formly whilst injecting script tags.

  1. <script src="bower_components/angular/angular.js"></script>
  2. <script src="bower_components/api-check/dist/api-check.js"></script>
  3. <script src="bower_components/angular-formly/dist/formly.js"></script>

Angular Formly can be downloaded from Bower package manager:

  1. $ bower install angular api-check angular-formly --save

Angular Formly uses api-check.js to check syntaxes and types of javascript objects. In specific places it is prohibited to use custom properties, in other places only predetermined types are allowed. This way it pursues to reduce the potential for error and improves code organization.

Components

Angular Formly contains angular services and directives itself that can be used according to your own discretion. The most important ones are the followings:

- formlyConfig service is for configuration.
- formlyConfigProvider is also for configuration but it is used in Angular’s config phase
- formly-form directive is for creating form
- formlyValidationMessages is for handling validation messages

Creating formly form

Formly isn’t shipped with predefined templates or fields hence the first step is to obtain or create field templates. Several template package is available to download and install from Bower package manager, inter alia, Bootstrap, LumX and Ionic.

Custom templates can be made with formlyConfig service in the following way:

  1. formlyConfig.setType({
  2.         name: 'input',
  3.         template: '<input ng-model="model[options.key]" />'
  4. });

Then use a template inside your controller:

  1. vm.fields = [
  2.         {
  3.                 type: 'input'
  4.         }
  5. ];

Use Formly directive to create a form:

  1. <form name="form" ng-submit="handleSubmit()">
  2. <formly-form model="model" fields="fields" form="form">
  3.         <button type="submit" ng-disabled="form.$invalid">Submit</button>
  4. </formly-form>
  5. </form>

How to apply Angular Formly to your project

It is recommendable to wrap Formly in an arbitrary angular directive. This way multiple modifications caused by a version change can be avoided. Furthermore, the controller of the created directive gives you a great place for define template types and other configurations. However, configurations can also can be placed in the run or config phase of the Angular application.
Formly allows to place html elements e.g. buttons inside the formly-form directive tag (as above). If you wrap Formly inside your own arbitrary directive, remember to have transcluding enabled and provide a transclude tag in order to make certain of the inclusion of the desired html elements in your form.

How to apply api-check to your project

In case you intend to use api-check in your Angular project, a possible option is to define it as an Angular constant then injecting it as you do with any other component.

  1. app.constant('apiCheck', apiCheck());

Model

Formly model can be reached with indexes by default. In the above case the field value can be addressed this way:

  1. vm.model[0]

This initial state can be redefined by adding key property to the field:

  1. vm.fields = [
  2.         {
  3.                 key: 'code'
  4.                 type: 'input'
  5.         }
  6. ];

After that modification, the field value can be addressed in the following way:

  1. vm.model.code

For simpler fields where neither model nor validation is used, you can turn off formControl to increase Formly’s efficiency:

  1. vm.fields = [
  2.         {
  3.                 noFormControl: true,
  4.                 template: '<p>simple text</p>'
  5.         }
  6. ];

Hide

Hiding a field is quite simple:

  1. vm.fields = [
  2.         {
  3.                 type: 'input',
  4.                 hide: true
  5.         }
  6. ];

When hiding a field, Formly uses the ng-if directive of Angular. It means if a field is hidden it is also removed from the dom. Considering this operation you can decide if Formly should use 'ng-show' directive instead in the following way:

  1. formlyConfig.extras.defaultHideDirective = 'ng-show';

If you need conditional hiding you can use hideExpression property. The value of this property can also be a string or a function. HideExpression executes when any model value changes. The function will provide us a scope variable which refers to the field’s scope.

  1. vm.fields = [
  2.         {
  3.                 type: 'input',
  4.                 hideExpression: function($viewValue, $modelValue, scope){
  5.                         return scope.hideSwitch;
  6.                 }
  7.         }
  8. ];
  9. vm.fields = [
  10.         {
  11.                 type: 'input',
  12.                 hideExpression: '!model.code'
  13.         }
  14. ];

Dynamic property assignment

The properties of a formly field can be changed dynamically by using expressionProperties object. However, using hide property is not recommended in expressionProperties because of a cooperational problem with ’ng-if’ directive. Dynamic hiding can be solved by using hideExpression property. Expressions run every time when a model value changes therefore pay attention to put only simple functions in it.

  1. vm.fields = [
  2. {
  3.         type: 'input',
  4.         templateOptions: {
  5.                 label: 'Default text:',
  6.                 required: false
  7.         },
  8.         data: {
  9.                 shouldBeRequired: false
  10.         },
  11.         expressionProperties:  {
  12.                 'templateOptions.label': '$viewValue',
  13.                 'templateOptions.required':function($viewValue, $modelValue, scope){
  14.                         return !scope.to.data.shouldBeRequired;
  15.                 }
  16.         }
  17. }
  18. ];

The function will provide a scope variable which refers to the field’s scope. In the field’s scope you can reach templateOptions by the ’to’ property.

ExpressionProperties can also be triggered manually by calling the function runExpressions() on a specific field, however it will be needed only in exceptional cases.

Data

Building on formly restricions, only specific properties can be defined which is given by formly and we can’t specify them according to our desire, in exchange formly provides us a data property which is reserved for developers to use freely for their purpose.

  1. vm.fields = [
  2.         {
  3.                 type: 'input',
  4.                 data:  {
  5.                         fourLegged: 'dog, cow',
  6.                         numberOfAnimals: 2
  7.                 }
  8.         }
  9. ];

Wrapper

Wrappers provide many variety of possibilities to combine html elements and increase code reusability. Wrapper property can be added at type declaration as well as field declaration however the wrapping order deserves to be shown in the following example.

  1. formlyConfig.setWrapper({
  2.         name: 'firstWrapper',
  3.         template: [
  4.                 '<div class="firstClass">',
  5.                 '<formly-transclude></formly-transclude>',
  6.                 '</div>'
  7.         ].join('')
  8. });
  9. formlyConfig.setWrapper({
  10.         name: 'secondWrapper',
  11.         template: [
  12.                 '<div class="secondClass">',
  13.                 '<formly-transclude></formly-transclude>',
  14.                 '</div>'
  15.         ].join('')
  16. });
  17.  
  18. formlyConfig.setType({
  19.         name: 'input',
  20.         wrapper: 'firstWrapper',
  21.         template: '<input ng-model="model[options.key]" />'
  22. });
  23.  
  24. vm.fields = [
  25.         {
  26.                 type: 'input',
  27.                 wapper: 'secondWrapper'
  28.         }
  29. ];

Html output:

  1. <div class="secondClass">
  2.         <div class="firstClass">
  3.                 <input ng-model="code" />
  4.         </div>
  5. </div>

Custom attributes

Formly makes possible to define custom attributes in the following way. More and detailed explanations can be found on angular-formly’s website.

  1. vm.fields = [
  2.         {
  3.                 type: 'input',
  4.                 templateOptions: {
  5.                         customAttribute: 3
  6.                 },
  7.                 ngModelAttrs: {
  8.                         customAttribute: {
  9.                                 attribute: 'custom-attribute'
  10.                         }
  11.                 }
  12.         }
  13. ];

Html output:

  1. <input ng-model="model[options.key]" custom-attribute="3" />

Watcher

Formly watchers are triggered when the model value of the field changes, runs only once per value change. While expressionProperties share a single angular watcher, this declaration creates a new one thus each of them will slower angular’s $digest life cycle as well as the application.

  1. vm.fields = [
  2. {
  3.         type: 'input',
  4.         watcher:  {
  5.                 listener: function(field, newValue, oldValue, scope, stopWatching){
  6.                         if(newValue) doSomethingImportant();
  7.                 }
  8.         }
  9. }
  10. ];

How to handle ’required’ validation

Required validation message can be added globally with the help of formlyValidationMessages:

  1. formlyValidationMessages.addStringMessage('required', 'Field is required!');

However you can define messages for each field separately. This way you can also overwrite the global message:

  1. vm.fields = [
  2.         {
  3.                 type: 'input',
  4.                 templateOptions: {
  5.                         required: true
  6.                 },
  7.                 validation: {
  8.                         messages: {
  9.                                 required: '"Field is required, really!"'
  10.                         }
  11.                 }
  12.         }
  13. ];

Setting of required attribute in templateOptions property is also required. Messages can be reached with formlyValidationMessages in the following way:

  1. formlyValidationMessages.messages['required']

To turn off the default html5 validation tooltip ’Fill out this field!’, use ’novalidate’ attribute on the form:

  1. <form name="form" ng-submit="handleSubmit()" novalidate>
  2. <formly-form model="model" fields="fields" form="form">
  3.         ...
  4. </formly-form>
  5. </form>

How to display validation messages with the help of ngMessages module of Angular

In order to use ngMessages, the module installation is required from Bower package manager, then injecting it into the application is needed.

It is recommendable to place validation messages html in a wrapper hence it will become reusable for other fields.

  1. formlyConfig.setWrapper({
  2.         name: 'validationWrapper',
  3.         template: [
  4.  
  5. '<formly-transclude></formly-transclude>',
  6. '<div ng-messages="fc.$error" ng-if="form.$submitted || options.formControl.$touched"',
  7. '<div ng-message="{{::name}}" ng-repeat="(name, message) in ::options.validation.messages">',
  8. '{{ message(options.formControl.$viewValue, options.formControl.$modelValue, this) }}',
  9. '</div>',
  10. '</div>'
  11.  
  12.         ].join('')
  13. });

Custom validation

Validation runs every time when the model value of the field changes. Validators can be functions or expression strings, as well as expression and message properties can also be functions or expression strings.

  1. vm.fields = [
  2. {
  3.         type: 'input',
  4.         validators: {
  5.                 checkSomething: {
  6.                         expression: function($viewValue, $modelValue, scope){
  7.                                 var isValid = false;
  8.                                 return isValid;
  9.                         },
  10.                         message: '$viewValue + " is not valid!"'
  11.                 },
  12.                 checkSomethingElse: '$viewValue !== "ValidCode"'
  13.         }
  14. }
  15. ];

Using optionsTypes

OptionsTypes attribute also intends to increase code reusability. Extra types can be created which offer additional options. Then options of any other type can be extended with this extra type.

  1. formlyConfig.setType({
  2.  
  3. name: 'matchField',
  4. defaultOptions: {
  5.         validators: {
  6.                 fieldMatch: {
  7.                         expression: function($viewValue, $modelValue, scope) {
  8.                                 var value = $modelValue || $viewValue;
  9.                                 return value === scope.model.code;
  10.                         },
  11.                         message: '"Not valid!"'
  12.                 }
  13.         }
  14. }
  15.  
  16. });
  17. vm.fields = [
  18.         {
  19.                 type: 'input',
  20.                 key: 'code'
  21.         },
  22.         {
  23.                 type: 'input',
  24.                 key: 'code2',
  25.                 optionsTypes: 'matchField'
  26.         }
  27. ];

Shortcuts

Formly provides shortcuts in fields for specific and often used properties. The followings are the most important ones:

  1. options                 field configuration
  2. to                      options.templateOptions
  3. fc                      options.formControl

Link and controller

Similar to Angular, link and controller properties on a formly field can be defined.

Link is a function which is triggered after field compilation while controller provides a way to confer custom behaviour on a field. They can be defined at type definition as well as at field definition.

Controllers are invoked using Angular’s $injector function therefore it must be annotated correctly, otherwise it could cause a problem at code minification.

  1. formlyConfig.setType({
  2.         name: 'input2',
  3.         template: '<input ng-model="model[options.key]" />',
  4.         link: function() {
  5.                 $log.info('in link');
  6.         },
  7.         controller: ['$scope', function($scope) {
  8.                 $log.info('in controller');                    
  9.         }]
  10. });

Turn off Chrome autocomplete functionality

Turning off Chrome autocomplete functionality can be achieved in the following way:

  1. formlyConfig.extras.removeChromeAutoComplete=true

Production

In production there are properties which are recommended to be set in order to improve formly efficiency. The most important ones are the followings:

Disable api-check before angular-formly is loaded:

  1. apiCheck.config.disabled=true

This setting will prevent running of specific formly development tools:

  1. window.onProd=true

This setting will reduce formly console output:

  1. formlyConfig.disableWarnings=window.onProd

Thank you for your attention!

Comments (0)
0