TL;DR
Go to Stackblitz and witness the power of@myndpm/dyn-forms
, check its synthetic source code and join the GitHub Discussions to design the upcoming features based on our experiences with Angular Forms.
As in most companies, at Mynd we build forms, filters, tables and display views for different purposes. We handle a ton of entities and we have custom components in our Design System to satisfy our needs. In this complex scenario, avoid boilerplate is a must, and to speed up the development process and facilitate the implementation and maintenance of these views, we built some base libraries to abstract the requirements into configuration objects that enable us to easily modify a form, a filter, a table, without touching a view template (most of the times).
So the question is: can we implement a standard, flexible enough layer to do this job and be shared with the Angular Community?
A bit of History
This challenge has been addressed by many developers and companies in many ways, we even have an official documentation guide on this topic; some approaches ends up with a template processing different types of fields with a ngSwitch
, others vary on the entrypoint component depending on the desired UI framework, or their config objects are not standardized and uses different field names for the same task on different controls. They are not completely generic, typed and/or extensible.
The ideal scenario is to have a strictly typed and serializable configuration object, so we are able store it in the state or the database without problems, as well as the ability to share some recipes with the community for common use-cases without complex functions involved, just a JSON object; there are a lot of good ideas out there, and we're in the process of discussing the best possible solutions for each topic.
Technically speaking, the challenge is to translate a Config Object (JSON
) into a functional Form (FormGroup
) being able to build any required nested structure, composing Control (inputs, selects, etc) into Containers to group them and customize the layout (cards, panels, etc).
What's New?
@myndpm/dyn-forms
is not just a "dynamic" forms library providing you a finite set of controls, or limiting your creativity and possibilities in any way. This library aims to be a quite generic and lightweight layer on the top of Angular's Form Framework, allowing us to build, extend and maintain our forms from their metadata, giving us more time to focus our attention on the business-logic requirements, custom validations, etc.
Moreover, we keep the control of our model and the Angular Form, manipulating the supported methods of FormGroup
, FormArray
and FormControl
, giving the responsibility of building the form hierarchy and its presentation to the library, but patching and listening any valueChanges
as we are used to.
Creating a DynForm
All we need is to import DynFormsModule
to our NgModule
and also provide the DynControls
that we need in our form. As a demostrative implementation, we mocked DynFormsMaterialModule
at @myndpm/dyn-forms/ui-material
to enable you right now to see how it works with some basic components:
import {
DynFormsMaterialModule
} from '@myndpm/dyn-forms/ui-material';
@NgModule({
imports: [
DynFormsMaterialModule.forFeature()
This package also provides a typed createMatConfig
Factory Method that (hopefully) will facilitate the development experience while creating configuration objects, by supporting type-checks (with overloads for the different controls):
import { createMatConfig } from '@myndpm/dyn-forms/ui-material';
@Component(...) {
form = new FormGroup({});
mode = 'edit';
config = {
controls: [
createMatConfig('CARD', {
name: 'billing',
params: { title: 'Billing Address' },
controls: [
createMatConfig('INPUT', {
name: 'firstName',
validators: ['required'],
params: { label: 'First Name' },
}),
createMatConfig('INPUT', {
name: 'lastName',
validators: ['required'],
params: { label: 'Last Name' },
}),
createMatConfig('DIVIDER', {
params: { invisible: true },
}),
...
now you're ready to invoke the Dynamic Form in your template
<form [formGroup]="form">
<dyn-form
[config]="config"
[form]="form"
[mode]="mode"
></dyn-form>
<button type="button" (click)="mode = 'display'">
Switch to Display Mode
</button>
</div>
Where the magic happens
The main feature is the ability to plug-in new Dynamic Form Controls, provide customized ones for some particular requirements, or integrate third-party components into our forms, with ease!
For this matter, Angular's InjectionTokens
are the way to apply the Dependency Inversion Principle, so we do not rely on the controls provided by a single library anymore, but any NgModule
(like DynFormsMaterialModule
) can provide new controls via the DYN_CONTROL_TOKEN
by registering the component to be loaded dynamically (DynControl
) with an "ID" (INPUT
, RADIO
, SELECT
, etc).
From there the Dynamic Form Registry can let the Factory
know what component it should load for a given "ID"
@Injectable()
export class DynFormRegistry {
constructor(
@Inject(DYN_CONTROLS_TOKEN) controls: ControlProvider[]
)
it's super hard to name these kind of "id" and "type" fields, so trying to keep the context clear, the ControlProvider
interface consists of:
export interface InjectedControl {
control: DynControlType;
instance: DynInstanceType;
component: Type<AbstractDynControl>;
}
- the
control
identificator is the 'string' to reference the dynamic control from the Config Object - the
instance
is the type ofAbstractControl
that it will create in the form hierarchy (FormGroup
,FormArray
orFormControl
), and - the
component
which should extend any of the Dynamic Control classes (DynFormGroup
,DynFormArray
,DynFormControl
orDynFormContainer
) implementing the simple contract explained here.
Configuration Object Typing
You can define your Form with an array of controls
which can have some subcontrols
; with this nested structure you can build any hierarchy to satisfy your needs (like in the example). This configuration unit is specified by the DynBaseConfig
interface which follows a simple Tree structure:
export interface DynBaseConfig<TMode, TParams> {
name?: string;
controls?: DynBaseConfig<TMode>[];
modes?: DynControlModes<TMode>;
}
The form also supports different "modes". Modes are partial overrides that we can apply to the main Control Configuration depending on a particular situation. In the simple-form demo we show an example of this: a display
mode where we define a readonly: true
parameter to be passed to all the dynamic controls, and they react changing their layout or styles. These "modes" are just a custom string
, so the configuration is open to any kind of mode
that you'd like to define.
In the DynFormConfig
you can specify the global override for each mode:
const config: DynFormConfig<'edit'|'display'> = {
modes: {
display: {
params: { readonly: true }
and you can also override the configuration of a single control for a given a mode, like this RADIO
button being changed to an INPUT
control when we switch the form to display
mode:
createMatConfig('RADIO', {
name: 'account',
params: { label: 'Create Account', color: 'primary' },
modes: {
display: {
control: 'INPUT',
params: { color: 'accent' },
In this case, the control
will be overriden but the params
will be merged and we will have the original label in the display
mode.
Feedback WANTED
With this brief introduction to this powerful library, we hope that you join its design/development efforts by sharing your experience/ideas/point of view in the GitHub Discussions opened for the upcoming features, creating Pull Request extending or adding new Material/TaigaUI/any controls, or reporting Issues that you find.
There are some challenges to be addressed, like a standard way to handle the Validations and show the respective Error message; handle the visibility of a control depending on some conditions; these topics have opened discussions to collect ideas and figure out a solution.
We might write more articles explaining the internals to analyze and improve the chosen architecture.
Without further ado, enjoy it!
// PS. We are hiring!
Top comments (9)
Hello!
I would like to know how to use this library with Boostrap and not with Angular Material.
Is there a way to do that? Or, if there is a useful resource for this, could you please point
me in the right direction?
Hi @toulix
we will need to add a new subpackage
@myndpm/dyn-forms/ui-bootstrap
with components in the Bootstrap way.Do you want to work with me adding that to the library?
Please fill a new Issue with the Controls you need and we start to create them together, while we document the experience for future UI packages ;)
github.com/myndpm/open-source/issu...
Hi Mateo,
I am sorry for my late response.
I am more than happy to work with you for this new package even though I don't have strong experience with Angular. And I want you to know that I am just a junior Angular developer, but I am enthusiastic about learning more and contribute with you. :)
Enthisiasm is good enough to start!
I invite you to join us on Discord: discord.gg/XxEqkvzeXg
and we can keep talking about the advances in this new ui-package.
I can build it and you add details to the controls ;)
Hi @matheo ,
I am interested in working with you on adding support for Tailwind.
Regards,
Orson
Nice library and nice article! Keep up the good work!
Really like this approach and wanna to try it out asap! Modes is a nice bonus for this stuff :)
great work, I am trying to load all the form control from a JSON file. The below code
controls I need to fetch from a JSON(infuture may be mongoDB). I am not sure if there is a better approach, any suggestions highly appreciated.
👏🔥Thanks for sharing this helpful resource, Mateo! Your approach to dynamic forms in Angular seems very promising and it's always exciting to see new ways to improve form creation and validation.I appreciate the clear step-by-step breakdown and explanations on how to use the DynFormsModule library. The demo makes it even easier to follow and implement.👍💡
💡📝 Recently, I came across an informative article on Dynamic Forms in Salesforce that offers a comprehensive overview of dynamic forms in Salesforce, including their benefits, limitations, and how to create them. It's a great resource for anyone looking to enhance their Salesforce forms