Dynamic forms with angular - improved

In my very first post (see Dynamic forms with angular) I explained how I manage to have dynamic forms in angular. Since that time I applied this solution several times and so far it made my angular programming days a bit easier.



In this post I will introduce another feature I added - the SlotHostComponent. It will allow you to inject custom form parts in your dynamic form. I normally try to group forms into meaningful fieldsets or in angular-ish FormGroups. I then create a Slot for each group. I basically treat a Slot as one fieldset in HTML language spoken.

And as you probably can imagine, having different Slot-types instead of only the default ones (Slot and ArraySlot) can be quite useful.

For the sake of this post I created a sample on StackBlitz that showcases the parts you need to respect in order to place a custom Slot within an angular form.

The “custom-slot.component” sample

The sample that I created does not actually do something meaningful. On the other hand it shows the possibilities I gained after having the SlotHostComponent implemented. If you open StackBlitz and go the the “Custom” route you will see a nested form with two Slots. The first one shows some personal data. The second one shows the json serialized output of the actual underlying Slot-data you have as a programmer and the respective Form-model the FormGroup carries.

Pieces you need

In order to implement a custom Slot you will need to prepare the following pieces:

  • a Slot-model: Within this model you define the structure of your FormGroup. It also inherits from the Slot class so we get all the properties to work with.
  • a custom-slot.component: Within this component we provide the markup we would like to be rendered for our custom form. It inherits from BaseSlotComponent so we get access to the underlying properties like the slot-property and the form-property.
  • the SlotComponentRegistry: This new service which is located within the formdef-directory is used to register custom Slots.
  • a host component of the actual form

Based on the list in the “Peaces you need” we are now going to look at some implementation details for the CustomSlotComponent sample.

The slot model

The CustomSlot model defines the structure of our form. It is a fairly simple form with a nested “Address” slot.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
export class CustomSlot implements Slot {
public static KEY = 'customslot';

public key = CustomSlot.KEY;
public type = SINGLE_SLOT;
public title = 'Custom slot';
public editors: Editor[];
public children: Slot[];

public constructor() {
this.editors = [
{
type: TEXT_EDITOR,
key: 'surname',
label: 'Surname',
required: true
},
{
type: TEXT_EDITOR,
key: 'lastname',
label: 'Last name',
maxLength: 20
},
{
type: CHECKBOX_EDITOR,
key: 'isDefault',
label: 'Use as default'
},
{
type: SELECT_EDITOR,
key: 'gender',
label: 'Gender',
required: true,
options: [
{ key: 'm', value: 'male' },
{ key: 'f', value: 'female' }
]
},
{
type: NUMBER_EDITOR,
key: 'year',
label: 'Year',
required: true,
min: 1900,
max: 2100
},
];
this.children = [
{
key: 'address',
type: CustomSlot.KEY,
title: 'Address',
editors: [
{
type: TEXT_EDITOR,
key: 'street',
label: 'Street'
},
{
type: TEXT_EDITOR,
key: 'zip',
label: 'Zip'
}
]
}
];
}

The custom-slot.component

The CustomSlotComponent-component is our main component of interest. Here we make advantage of the underlying slot and form- property in order to implement the functionality and behavior we would like to achieve.

In the template you can clearly see that we have direct access to the slot or form- property. For this dumb component and the demo’s sake we just serialize the slot value as a json string. I did the same with the form.value property to actually see their values on the screen.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
@Component({
selector: 'tw-custom-slot',
template: `
<h5>{{ slot.title }}</h5>
<p>
This is a custom slot that displays the available data you
could do stuff with within the component as json string.
</p>
<h6>Slot value</h6>
<pre>{{ slot | json}}</pre>
<h6>Form value</h6>
<pre>{{ form.value | json }}</pre>
`
})
export class CustomSlotComponent extends BaseSlotComponent {
}

The SlotComponentRegistry

In order to let the angular application know of our custom slot component we need to tell the SlotComponentRegistry about it.

The snippet below shows how the CustomSlotComponent needs to be registered.

1
2
3
4
5
6
7
8
9
10
11
12
13
export class CustomModule {
public constructor(
private _slotRegistry: FormdefRegistry,
private _slotComponentRegistry: SlotComponentRegistry
) {
this._slotRegistry.register(new CustomSlot());

this._slotComponentRegistry.register(new SlotComponentMetaData(
CustomSlot.KEY,
CustomSlotComponent
));
}
}

Don’t forget to add your custom slot component to the entryComponents!

The host component of the actual form

On this component we host the FormDef-element (see tw-formdef). This is usually the component you route to when to create or edit some object. In our case the model is the hard coded viewmodel-object.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
@Component({
selector: 'tw-custom',
template: `
<div class="row">
<div class="col-lg-6">
<tw-formdef
[key]="key"
[viewModel]="viewModel"
(submitted)="submitted($event)">
</tw-formdef>
</div>
<div class="col-lg-6">
<pre>{{ submittedViewModel | json }}</pre>
</div>
</div>
`
})
export class CustomComponent {
public key = CustomSlot.KEY;

public viewModel = {
surname: 'Thomas',
lastname: 'Duft',
isDefault: true,
gender: 'm',
year: 1980,
address: {
street: 'Some street',
zip: '12345'
}
};

...
}

Benefits

The above approach helped me to find the right mix between always writing angular forms from scratch over an over again and the dynamic forms generation approach I described in my first post. Now I have the possibility to create my own slots that differ from the default implementations.

The benefits in short I think are:

  • still have a generic data driven form generation approach
  • inject custom slots like:
    • chart slot that depends on slot data
    • different styling or arrangement of controls than default slots
  • direct access to slot data and FormGroup control instance within custom slot component

If you came up with some similar or other approach I would be interested in, just leave a comment.

I hope this post could help you in some kind or the other.

Cheers Thomas