Angular 4+, Empower Dynamic Form, Part 2: Make It Better

  • Previous, we have Type “My Lady” and “My Man”. Now, let’s add a group Sex to indicate how attractive the residents are. Group Sex has two DropDown elements: Type “My Lady” and “My Man”, and Sexy “Yes!” and “Oh, Yeah!”
  • A new group Address is added to record where the residents live. The group has one TextField element Street and one DropDown element Location.
import { MyModelParam } from './myModelParam';export class Address {
street: string;
location: string;
constructor(param : MyModelParam) {
this.street = param.street || 'guess...';
this.location = param.location || 'Heaven';
}
}
import { MyModelParam } from './myModelParam';
import { Type } from './type';
export class Sex {
type: string;
sexy: string;
constructor(param : MyModelParam) {
this.type = param.type || Type.MyLady.toString();
this.sexy = param.sexy || 'Yes!';
}
}
import { Type } from './type';export interface MyModelParam {
name?: string; //<-optional now
type?: string;
sexy?: string;
street?: string;
location?: string

}
import { Address } from './address';
import { MyModelParam } from './myModelParam';
import { Sex } from './sex';
import { Type } from './type';
export class MyModel {
name: string;
sex: Sex;
address: Address;
constructor(param : MyModelParam) {
this.name = param.name || '';
this.sex = new Sex({type: param.type, sexy: param.sexy});
this.address = new Address({street: param.street, location: param.location});

}
}
ng g component group-element
import { ElementParam } from '../form-element/elementParam';
import { FormElement } from '../form-element/formElement';
import { AbstractControl, FormGroup } from '@angular/forms';
export class Group extends FormElement {
elementType = 'group';
elements: FormElement[];
constructor(element: ElementParam) {
super(element);
this.elements = element.elements;
}
toFormControl(): AbstractControl {
return new FormGroup(this.toFormGroup(this.elements));
}
private toFormGroup(elements) {
let group: any = {};
elements.forEach((element: FormElement) => {
if (element.elementType === 'group') {
let subGrp: any = this.toFormGroup((element as Group).elements);
group[element.name] = new FormGroup(subGrp);
} else {
group[element.name] = element.toFormControl();
}
});

return group;
}
}
import { Type } from '../model/type';
import { FormElement } from './formElement';
export interface ElementParam {
name: string;
displayName?: string;
elementType?: string;
value?: any;
required?: boolean;
order?: number;
//'elements' field represents a group of elements
elements?: FormElement[];

layout?: any;
options?: string[];
}
import { DropDown } from '../form-element/dropDown';
import { FormElement } from '../form-element/formElement';
import { TextField } from '../form-element/textField';
import { Group } from '../group-element/group';
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 Group({
name: 'form',
layout: {width: '80%', fontSize: '12', fx: 'column' },
elements: [
new TextField({
name: 'name',
displayName: 'Name',
value: model.name,
layout: {float: 'left', width: '80%', fontSize: '16' }
}),
new Group({
name: 'sex',
displayName: 'Sex',
layout: {float: 'right', width: '80%', labelFontSize: '14', fx: 'column', fxAlign: 'start', lableFontWeight: 'bold' },
elements: [
new DropDown({
name: 'type',
displayName: 'Type',
value: model.sex.type,
options: [Type.MyLady.toString(), Type.MyMan.toString()],
layout: {float: 'left', width: '80%',fontSize: '16'}
}),
new DropDown({
name: 'sexy',
displayName: 'Sexy',
value: model.sex.sexy,
options: ['Yes!', 'Oh, Yeah!'],
layout: {float: 'left', width: '80%',fontSize: '16'}
}),
],
}),
new Group({
name: 'address',
displayName: 'Address',
layout: {float: 'right', width: '80%', labelFontSize: '14', fx: 'column', fxAlign: 'start', lableFontWeight: 'bold' },
elements: [
new TextField({
name: 'street',
displayName: 'Street',
value: model.address.street,
layout: {float: 'left', width: '80%', fontSize: '16' }
}),
new DropDown({
name: 'location',
displayName: 'Location',
value: model.address.location,
options: ['Heaven', 'Under the tree', 'On the green grass', 'Among the beautiful flowers', 'Down to earth'],
layout: {float: 'left', width: '80%', fontSize: '16' }
}),
],
})
]
})
];
let ret: FormGroup = this.toFormGroup(this.elements) as FormGroup;
return ret;
}
}
{
"form":{
"name":"Eve",
"sex":{
"type":"My Lady",
"sexy":"Yes!"
},
"address":{
"street":"guess...",
"location":"Heaven"
}
}
}
{
"form":{
"name":"Adam",
"sex":{
"type":"My Man",
"sexy":"Oh, Yeah!"
},
"address":{
"street":"801 Who-knows",
"location":"Under the tree"
}
}
}
  • group-element.component.html the HTML template for group elements
  • group-element.component.ts the class that supports the template
<div [formGroup]="group" style="border:0px;">  <mat-label fxLayoutAlign="{{element.layout.fxAlign}}" 
[ngStyle]="{'float': element.layout.float,
'font-size.px': element.layout.labelFontSize,
'font-weight': element.layout.lableFontWeight}">
{{element.displayName}}
</mat-label>
<!-- That is right. This is the plain dumb br here! I believe you could do better...-->
<br>
<mat-card class="card" fxLayout="{{element.layout.fx}}" fxLayoutGap="{{element.layout.fxGap}}"> <div *ngFor="let elemt of element.elements"> <div [ngSwitch]="elemt.elementType" class='panel-body' style="border:0px;"> <!-- This app-form-element allows the form elements in this group displayed -->
<app-form-element *ngSwitchDefault [group]="group" [element]="elemt" ></app-form-element>
<!-- This app-group-element allows the nested groups displayed -->
<app-group-element *ngSwitchCase="'group'" [group]="group.get(elemt.name)" [element]="elemt"></app-group-element>
</div>
</div>
</mat-card>
</div><br>
  • *ngFor iterates the elements in the group element element.
  • app-form-element renders the elements inside the group.
  • app-group-element renders the nested groups if there is any.
  • Note [formGroup]="group" , wheregroup represents the FormGroup of the group element being rendered.
  • Similarly, note [group]="group.get(elemt.name)", where [group] is the input of nested app-group-element for FormGroup. The group.get(elemt.name) retrieves the nested FormGroup for the nested group. (Remember in Group class, when we converted the configuration to FormGroup, we used the element name of Group as the nested group name.)
import { FormElement } from '../form-element/formElement';
import { Component, OnInit, Input } from '@angular/core';
import { FormGroup } from '@angular/forms';
@Component({
selector: 'app-group-element',
templateUrl: './group-element.component.html',
styleUrls: ['./group-element.component.css']
})
export class GroupElementComponent implements OnInit {

@Input() element: FormElement;
@Input() group: FormGroup;
constructor() { } ngOnInit() { }
}
<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}">
<div [ngSwitch]="elemt.elementType" class='panel-body' style="border:0px;"> <app-form-element *ngSwitchDefault [group]="form" [element]="elemt" ></app-form-element> <app-group-element *ngSwitchCase="'group'" [group]="form.get(elemt.name)" [element]="elemt"> </app-group-element>

</div>

</div>
<button mat-button type="submit">Submit</button> </div>
</form>
  • The bold section is the part changed from part 1 code. According to the element type elemt.elementType, this template renders the app-form-element or app-group-element accordingly.
  • The [group] input of app-group-element is used to denote the FormGroup that is associated with the Group element. The FormGroup for this app-group-element is retrieved by form.get(elemt.name).

--

--

--

Life is beautiful…

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

Recommended from Medium

JavaScript: A beginners guide, Day 2

Javascript: A beginners guide

How to build Web server in node Js

Web Server in Node js

Update #2: Star Dust Early Mint Pass

How to Convert Excel File into JSON in Javascript

Week 4 Javascript in review

[Leet Code] Group the People Given the Group Size They Belong To

Creating a Dynamic Page in Next.js

A Guide To Unit Test in Angular

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 Test Chart.js in an Angular Component

How to add Bootstrap to the Angular Project

Jest Integration with Angular 12

OpenId Connect (OIDC) with Angular