Dynamic forms with NgRx
Juan Stoppa
Software Engineer - Wealth Dynamix
@juanstoppa
My journey with forms
@juanstoppa
"Digital Transformation"
@juanstoppa
What does it mean for the user?
@juanstoppa
Web Forms
Paper forms
@juanstoppa
Agenda
- Define the requirements
- Define a solution
- Show a few demos
- Q&A
@juanstoppa
The requirements
- Dynamic validation
- Extensible with custom components
- Design system agnostic
- Support multiple layouts
@juanstoppa
Proposed solution
NgRx
Dynamic Reactive forms
Dynamic component loader
CSS grid
@juanstoppa
Dynamic reactive forms
@juanstoppa
Dynamic reactive forms
let questions: QuestionBase < any > [] = [
new TextboxQuestion({
key: 'firstName',
label: 'First name',
value: 'Bombasto',
required: true,
order: 1
}),
new TextboxQuestion({
key: 'emailAddress',
label: 'Email',
type: 'email',
order: 2
}),
new DropdownQuestion({
key: 'brave',
label: 'Bravery Rating',
options: [
{ key: 'solid', value: 'Solid' },
{ key: 'great', value: 'Great' },
{ key: 'good', value: 'Good' },
{ key: 'unproven', value: 'Unproven' }],
order: 3
})];
@juanstoppa
Dynamic reactive forms
export class QuestionBase<T> {
id: string;
value: T;
key: string;
label: string;
order: number;
controlType: string;
rules: FormRules;
position: GridPosition;
}
@juanstoppa
Demo
@juanstoppa
Ngrx
Ngrx store
Questions
Template
2
Form
Groups
Components
3
4
Initial state is loaded
Form Groups recalculated with new state
Data
1
Dispatch [form] load action
@juanstoppa
Ngrx
Ngrx store
Data
Form
Components
Validation rules
let questions: QuestionBase < any > [] = [
new TextboxQuestion({
key: 'firstName',
label: 'First name',
value: 'Bombasto',
required: true,
order: 1
}),
new TextboxQuestion({
key: 'emailAddress',
label: 'Email',
type: 'email',
order: 2,
rules: {
required: {},
hidden: {},
readonly: {
condition: "!firstName"
}
}
}),
new DropdownQuestion({
key: 'brave',
label: 'Bravery Rating',
options: [
{ key: 'solid', value: 'Solid' },
{ key: 'great', value: 'Great' },
{ key: 'good', value: 'Good' },
{ key: 'unproven', value: 'Unproven' }],
order: 3
})];
@juanstoppa
Component loader
Container
Component
Representational
Component
@Input
@Ouput
const componentFactory = this.fr.resolveComponentFactory(component);
const ref = viewContainerRef.createComponent(componentFactory);
// @Input
(<QuestionComponent>ref.instance).input = this.component;
// @Output
this.formControl.controls[this.component.id].valuesChanges.subscribe(...
@juanstoppa
Component loader
export class DynamicFormQuestionComponent implements OnInit {
@Input() question: QuestionBase<any>;
@Input() form: FormGroup;
@ViewChild('content', { read: ViewContainerRef, static: true})
content: ViewContainerRef;
constructor(
private cfr: ComponentFactoryResolver,
private vcr: ViewContainerRef
) {}
ngOnInit(): void {
const factories = Array.from(this.cfr['_factories'].keys());
const type = <Type<Component>>factories.
find((x: any) => x.componentName === this.question.controlType);
const component = this.vcr.
createComponent(this.cfr.resolveComponentFactory(type));
(<any>component).instance.question = this.question;
(<any>component).instance.form = this.form;
this.content.insert(component.hostView);
@juanstoppa
CSS Grid
.grid-container {
display: grid;
grid-template-areas:
'header header'
'left right'
'bottom bottom';
}
.header { grid-area: header; }
.left { grid-area: left; }
.right { grid-area: right; }
.across { grid-area: bottom; }
<div class="grid-container">
<div class="header">Header</div>
<div class="left">Field 1</div>
<div class="right">Field 2</div>
<div class="bottom">Field 3</div>
</div>
@juanstoppa
What about a real life example?
@juanstoppa
Level up the form
Component
Component
Section
Page
Page
Layout
Section
Form Metadata
Form Metadata
@juanstoppa
Level up the form
Component
Component
Section
Page
Page
Layout
Section
FormControl
FormControl
FormGroup
FormGroup
FormGroup
FormGroup
FormGroup
Store Service
Component
Form Metadata
RF FormGroup
Store Service dictates validation rules with new state
FormGroup applies validation rules from new state
@juanstoppa
Conclusion
Ngrx as one source of truth
Reactive forms FTW
Dynamic component loader for extending
CSS grid for custom layouts
Thank you!
Juan Stoppa
@juanstoppa
FormQL - https://github.com/formql/formql
Dynamic Form example - https://github.com/jstoppa/dynamic-forms
References
CSS Grid template - https://developer.mozilla.org/en-US/docs/Web/CSS/grid-template-areas
NgRx docs - https://ngrx.io/docs
Dynamic forms with NgRx - Angular London Meetup
By Juan Stoppa
Dynamic forms with NgRx - Angular London Meetup
- 1,080