Friday, February 24, 2017

A "Generic Component" in Angular 2

I am generally a lazy person. Especially so when it comes to writing some UI code (although I can have endless comments and critiques on someone else's UI and UX ;-)) . I find it repetitive. And any form of repetitive behaviour is ripe for automation. One such activity which I recently encountered in a project I was doing was creating forms - a lot of them - with Angular 2 front end. I remember in good old MS Access days these were just a click away - although I disliked it, this is what I did in my first paying job in summer break of my school.
I am not sure we have something equivalent here - more so when Angular is so rapidly changing and keeps breaking every other month.

However, it is rather easy to write a "generic component" that can be simply configured using a JSON and you can have a new form without writing any of the usual code. This has two main advantages: 1) You have a single place to fix when Angular changes something in its structure 2) You have a super reusable form engine which can be configured on the fly, allowing you to do cool things like storing form info in a backend service, and automatically update the Angular 2 app on the fly.

To start off, you will need to define a JSON which can be used to construct the UI on the fly.

this.componentJSON = {};
this.componentJSON['title'] = "Trials";

this.componentJSON['formItems'] = [
 { "type": "text", "id": "trial", "name": "Trial Name", "description": "Name of trial", "theValue": "", "param_name": "trial_name" },
 { "type": "text", "id": "sponsorer", "name": "Sponsorer", "description": "Sponsorer of the trial", "theValue": "", "param_name": "sponsorer_name" },
 { "type": "button", "id": "submit", "name": "Submit", "description": "Submit the form", "api_call": this.createTrial },
];


The above JSON is intended to create a simple form with a title, two text fields and a button. The api_call parameter of the button is a Service object used to make an API call.  This JSON should be used in any parent component that intends to use this component, typically being initialised in ngOnInit() method.

Next we define generic.component.ts and generic.component.html as follows

import { Component, OnInit, Input } from '@angular/core';
import { FormsModule } from '@angular/forms';
import { CommonModule } from '@angular/common';
import { BrowserModule } from '@angular/platform-browser';

@Component({
   moduleId: module.id,
   selector: 'generic-cmp',
   templateUrl: 'generic.component.html'
})
export class GenericComponent implements OnInit {

   @Input()
   componentJSON: any;

   constructor() { }

   ngOnInit() {
   }

   callAPI(item: any) {
     item.api_call.api_call(this.processInput(this.componentJSON)).subscribe((res:any) => {      
if (res.status == 0) {
        alert(res.result.message);  
} else {
        alert(res.error.error_message);  
}
     });
   }

   processInput(componentJSON: any) {
     var formItems = componentJSON['formItems'];

     var params: any;
     params = {};
     for(var frmItm in formItems) {
if (formItems[frmItm].type != 'button') {
        params[formItems[frmItm].param_name] = formItems[frmItm].theValue;
}
     }

     return params;
   }
}













The trick as always is to generalise the JSON and then the generic component generator above to take care of different input forms as well as adding validations. The callAPI function above for instance, basically generalises an API call, where as the processInput method creates the parameter payload for API call from the JSON we created earlier. Advantage again being that simply changing the JSON pretty much re-creates the whole of the HTML. Creating a different form just requires one to define a new JSON.

Since this component needs to be used in multiple places it would be wise to declare the directives associated with this component in a shared.module.ts file:

import { NgModule, ModuleWithProviders } from '@angular/core';
import { CommonModule } from '@angular/common';
import { FormsModule } from '@angular/forms';
import { RouterModule } from '@angular/router';
import { BrowserModule } from '@angular/platform-browser';

import { NameListService } from './name-list/index';
import { GenericComponent } from './generic-component/generic.component';

@NgModule({
    imports: [CommonModule, RouterModule, FormsModule],
    declarations: [GenericComponent],
    exports: [CommonModule, FormsModule, RouterModule, BrowserModule, GenericComponent],    
})

export class SharedModule {
    static forRoot(): ModuleWithProviders {
        return {
            ngModule: SharedModule,
            providers: [NameListService]
        };
    }
}


Now a third component can embed this reusable, configurable component as using:



Thats it! You should have a fairly reusable forms module, that you can fully configure using the JSON, without the need to keep writing the HTML and related Component code every time. 

A similar pattern may also be used for creating a generic service call. This is again useful as you code can remain independent of the changes that may occur in basic underlying syntax of say actually calling the HTTP post method. 

No comments: