Angular 4+, Empower Dynamic Form, Part 1: A Simple One

$ ng new playground
$ cd playground
  • node_modules has all the node modues (eh… seems quite obvious)
  • src has all your source codes (not yet, since this is an brand new baby)
  • package.json has the package dependency configuration. It has a follower package-lock.json, which will change together with package.json when you have new package installed through npm command. These two files update automatically if you install new packages or update existing packages to a new version. You could modify them manually, but I prefer to have them managed by npm automatically.
  • tsconfig.json & tslint.json you can play with these two files for typescript configurations.
$ npm i --save @angular/flex-layout
$ npm i --save @angular/material
$ npm i --save @angular/cdk
$ npm i --save hammerjs
import 'hammerjs/hammer';
@import '~@angular/material/prebuilt-themes/indigo-pink.css';
import { BrowserModule } from '@angular/platform-browser';
import { NgModule } from '@angular/core';
import { AppComponent } from './app.component';
import { FormElementComponent } from './form-element/form-element.component';
import { FormComponent } from './form/form.component';
import { FlexLayoutModule } from '@angular/flex-layout';
import { FormsModule, ReactiveFormsModule } from '@angular/forms';
import { BrowserAnimationsModule } from '@angular/platform-browser/animations';
import {
MatAutocompleteModule,
MatButtonModule,
MatButtonToggleModule,
MatCardModule,
MatCheckboxModule,
MatChipsModule,
MatDatepickerModule,
MatDialogModule,
MatExpansionModule,
MatFormFieldModule,
MatGridListModule,
MatIconModule,
MatInputModule,
MatListModule,
MatMenuModule,
MatNativeDateModule,
MatPaginatorModule,
MatProgressBarModule,
MatProgressSpinnerModule,
MatRadioModule,
MatRippleModule,
MatSelectModule,
MatSidenavModule,
MatSliderModule,
MatSlideToggleModule,
MatSnackBarModule,
MatSortModule,
MatTableModule,
MatTabsModule,
MatToolbarModule,
MatTooltipModule,
MatStepperModule,
} from '@angular/material';
@NgModule({
declarations: [
AppComponent,
FormElementComponent,
FormComponent
],
imports: [
BrowserModule,
FlexLayoutModule,
FormsModule,
ReactiveFormsModule,
BrowserAnimationsModule,
MatAutocompleteModule,
MatButtonModule,
MatButtonToggleModule,
MatCardModule,
MatCheckboxModule,
MatChipsModule,
MatDatepickerModule,
MatDialogModule,
MatExpansionModule,
MatFormFieldModule,
MatGridListModule,
MatIconModule,
MatInputModule,
MatListModule,
MatMenuModule,
MatNativeDateModule,
MatPaginatorModule,
MatProgressBarModule,
MatProgressSpinnerModule,
MatRadioModule,
MatRippleModule,
MatSelectModule,
MatSidenavModule,
MatSliderModule,
MatSlideToggleModule,
MatSnackBarModule,
MatSortModule,
MatTableModule,
MatTabsModule,
MatToolbarModule,
MatTooltipModule,
MatStepperModule,
],
providers: [],
bootstrap: [AppComponent]
})
export class AppModule { }
export enum Type {
MyLady = 'My Lady', MyMan = 'My Man'
}
export interface MyModelParam {
name: string;
type?: string;
}
import { MyModelParam } from './myModelParam';
import { Type } from './type';
export class MyModel {
name: string;
type: string;

constructor(param : MyModelParam) {
this.name = param.name || '';
this.type = param.type || Type.MyLady.toString();
}
}
$ ng g component form-element
  • form-element.component.css: Cascading Style Sheets file for the HTML page
  • form-element.component.html: the HTML page
  • form-element.component.spec.ts: I don’t know, and I don’t care.
  • form-element.component.ts: The typescript class that backs the HTML page
export interface ElementParam {
name: string;
displayName?: string;
elementType?: string;
value?: any;
required?: boolean;
order?: number;
layout?: any;
options?: string[];
}
import { ElementParam } from './elementParam';
import { FormControl, AbstractControl } from '@angular/forms';
export class FormElement {
value: any;
name: string;
displayName: string;
required: boolean;
order: number;
elementType: string;
layout: any;
constructor(element: ElementParam) {
this.value = element.value;
this.name = element.name || '';
this.displayName = element.displayName || '';
this.required = element.required;
this.order = element.order || 1;
this.elementType = element.elementType || '';
this.layout = element.layout || {float: 'left', width: '100%' };
}
toFormControl(): AbstractControl {
return new FormControl(this.value);
}
}
import { FormElement } from './formElement';export class TextField extends FormElement {  elementType = 'textField';  constructor(element: ElementParam) {
super(element);
}
}
import { ElementParam } from './elementParam';
import { FormElement } from './formElement';
export class DropDown extends FormElement {
elementType = 'dropDown';
options: string[];
constructor(element: ElementParam) {
super(element);
this.options = element.options || [];
}
}
  • A HTML page form-element.component.html that carries out the HTML elements, and
  • The component class form-element.component.ts to back the HTML page.
<div [formGroup]="group">  <div [ngSwitch]="element.elementType">    <mat-form-field *ngSwitchCase="'textField'" appearance="outline"        [ngStyle]="{'float': element.layout.float, 'font-size.px': element.layout.fontSize}">      <mat-label>{{element.displayName}}</mat-label>      <input id="{{element.name}}" matInput    
[formControlName]="element.name"/>
</mat-form-field> <mat-form-field *ngSwitchCase="'dropDown'" appearance="outline" [ngStyle]="{'float': element.layout.float, 'font-size.px': element.layout.fontSize}"> <mat-label>{{element.displayName}}</mat-label> <mat-select id="{{element.name}}" [formControlName]="element.name"> <mat-option *ngFor="let opt of element['options']" [value]="opt">{{ opt }}</mat-option> </mat-select> </mat-form-field> </div></div>
import { FormElement } from './formElement';
import { Component, OnInit, Input } from '@angular/core';
import { FormGroup } from '@angular/forms';
@Component({
selector: 'app-form-element',
templateUrl: './form-element.component.html',
styleUrls: ['./form-element.component.css']
})
export class FormElementComponent implements OnInit {
@Input() element: FormElement;
@Input() group: FormGroup;
constructor() {}
ngOnInit() {}
}
  • This component’s selector is app-form-element as indicated in form-element.component.ts, meaning, in the HTML page that uses this component, <app-form-element></app-form-element> summons the present of this component.
  • The HTML page form-element.component.html is backed by field variable element and group of the class form-element.component.ts.
  • These two variables element and group are marked as @input , which allows these two fields being used as component input in the HTML page that uses this component, such as: <app-form-element [element]=”elemt” [group]=”form”></app-form-element>
  • All the elements in the same form are bound to the same formGroup. The component HTML page uses [formGroup]="group" to tell us that the element belongs to a formGroup, whose name is group. The group is an variable of type FormGroup, which is defined in component class form-element.component.ts.
  • The elementhere is a FormElement, if you scroll up to the Form configuration elements section, you will find that we have TextFieldand DropDown elements defined.
  • The HTML element is selected by [ngSwitch]="element.elementType" . *ngSwitchCase="'textField'" tell us, when the elementType is 'textField' , the mat-form-field with input label & element is displayed, *ngSwitchCase="'dropDown'" tell us, when the elementType is dropDown , the mat-form-field with mat-select label & element is displayed.
  • [ngStyle] is used to control the display style of the elements.
ng g component form
<form (ngSubmit)="submit()" [formGroup]="form" >  <div style="width: 40%; margin: auto;">    <div *ngFor="let elemt of elements" 
[ngStyle]="{'float': elemt.layout.float, 'width': elemt.layout.width}">
<app-form-element [element]="elemt" [group]="form"></app-form-element> </div> <button mat-button type="submit" class="btn-success">Submit</button> </div></form>
import { FormElement } from '../form-element/formElement';
import { MyModel } from '../model/myModel';
import { FormFactory } from './formFactory';
import { Component, OnInit } from '@angular/core';
import { FormGroup } from '@angular/forms';
@Component({
selector: 'app-form',
templateUrl: './form.component.html',
styleUrls: ['./form.component.css']
})
export class FormComponent implements OnInit {

elements: FormElement[];
form: FormGroup;
formFactory: FormFactory;
model: MyModel;
constructor() {}ngOnInit() {}public createForm(model: MyModel) {
this.formFactory = new FormFactory();
this.form = this.formFactory.createForm(model);
this.elements = this.formFactory.elements;
}

public submit() {
this.model = this.form.value;
console.log('Model: ' + JSON.stringify(this.model));
//do your submitting job to backend API
}
}
  • In HTML file, we use the app-form-element component to represent one of the form elements. For different element types, it has corresponding element presentation.
  • app-form-elementhas 2 input parameters [element]="elemt"and [group]="form". Where, when the form has multiple elements, elemt is one of the elements. group is used to assign a FormGroup form for the form element.
  • When the form has multiple elements, *ngFor="let elemt of elements" is used to iterate the elements .
  • elements is the class field of form.component.ts . The elements field is an array of FormElement. elements is populated after the createForm method is called.
  • form is the FormGroup that is associated with the HTML form, which is populated when the createForm method is called.
  • [ngStyle] is used to control the display style of the elements.
  • The selector of form component is app-form.
import { DropDown } from '../form-element/dropDown';
import { FormElement } from '../form-element/formElement';
import { TextField } from '../form-element/textField';
import { MyModel } from '../model/myModel';
import { Type } from '../model/type';
import { FormGroup, AbstractControl, FormControl } from '@angular/forms';
export class FormFactory { elements: FormElement[]; private toFormGroup(elements: FormElement[]): AbstractControl {
let group: any = {};
elements.forEach((element: FormElement) => {
group[element.name] = element.toFormControl();
});

return new FormGroup(group);
}
public createForm(model: MyModel): FormGroup {
console.debug('creating form...');
this.elements = [
new TextField({
name: 'name',
displayName: 'Name',
value: model.name,
layout: {float: 'left', width: '80%', fontSize: '16' }
}),
new DropDown({
name: 'type',
displayName: 'Type',
value: model.type,
options: [Type.MyLady.toString(), Type.MyMan.toString()],
layout: {float: 'left', width: '80%', fontSize: '16' }
}),
];
let ret: FormGroup = this.toFormGroup(this.elements) as FormGroup;
return ret;
}
}
  • value field in the form element is used to assign the initial value of the element from model.
  • layout configures the element look-and-feel, which is used by [ngStyle] in the HTML template form-element.component.html.
  • toFormGroup method converts the form element configuration to the FormGroup.
<div style="text-align:center">  <h3>    Welcome to {{ title }}!  </h3></div><app-form></app-form>
import { FormComponent } from './form/form.component';
import { MyModel } from './model/myModel';
import { Type } from './model/type';
import { Component, ViewChild, OnInit } from '@angular/core';
@Component({
selector: 'app-root',
templateUrl: './app.component.html',
styleUrls: ['./app.component.css']
})
export class AppComponent implements OnInit {
title = 'The Garden of Eden Registration';
@ViewChild(FormComponent) form: FormComponent;
ngOnInit() {
//To edit existing form, you could retrieve the
//model from backend REST API, and use it to create form here.
this.form.createForm(new MyModel({name: 'Eve', type: Type.MyLady}));
}
}

--

--

--

Life is beautiful…

Love podcasts or audiobooks? Learn on the go with our new app.

Recommended from Medium

Performance Comparison of Backend Programming Languages

C#, WPF, and Fixed Documents. Let’s Talk Printing.

Xpansion Game Announces Mainnet Launch On November 30th

Build a Twitter Bot with Python and Tweepy

Azure Pipelines catch invalid PBIX file versions

BlogPost_302

What about CLI applications?

Scrum vs Kanban Methodology in Software Documentation

Get the Medium app

A button that says 'Download on the App Store', and if clicked it will lead you to the iOS App store
A button that says 'Get it on, Google Play', and if clicked it will lead you to the Google Play store
Tom Liu

Tom Liu

Life is beautiful…

More from Medium

How to Edit Context Menu in AG Grid

A simple guide to authenticate an Angular application with Asgardeo

Angular : Dynamically resolving service based on text/enum value

OpenId Connect (OIDC) with Angular