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

$ ng new playground
$ cd playground
$ 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
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 || [];
}
}
<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() {}
}
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
}
}
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;
}
}
<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}));
}
}

--

--

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