Let’s create an Angular Formly form modal.
With Angular we can create forms in many different ways, depending on what we need to do and depending on the complexity of the request.
The library we are going to use today helps us in the generation of dynamic forms that allow the user to enter data, even very complex ones.
The ingredients for our recipe are 3 and they are the following:
- input with button to open the modal
- a wrapper that simulates a modal
- a button that sends what is inserted into the modal
1) Modal wrapper for Formly form
One situation that we can have, and that can be among the most complicated, is a modal inside our form. A way to answer to this problem is to create a wrapper for the form fields that will end up in it.
modal-wrapper.component.ts
import { Component } from '@angular/core';
import { FieldWrapper } from '@ngx-formly/core';
@Component({
selector: 'modal-wrapper',
templateUrl: './modal-wrapper.component.html',
})
export class ModalWrapperComponent extends FieldWrapper {
onClose($event) {
if (this.to.onClose) {
this.to.onClose($event);
}
}
}
It extends the FieldWrapper
of Formly.
modal-wrapper.component.html
<div class="formly-modal">
<div class="modal show"
tabindex="-1" role="dialog" aria-labelledby="dialog-events-name">
<div class="modal-dialog">
<div class="modal-content">
<div class="modal-header">
<button type="button" class="close" (click)="onClose($event)" aria-label="Close">
<span class="close-icon">×</span>
</button>
<h4 class="modal-title" >
{{to.title}}
</h4>
</div>
<div class="modal-body">
<div [innerHTML]="to.description"></div>
<ng-container #fieldComponent></ng-container>
</div>
</div>
</div>
<div class="modal-backdrop fade in" (click)="onClose($event)"></div>
</div>
</div>
2) Submit modal button for a Formly form modal
We have also to create a custom button to do actions inside the modal. We’ll put all inside the component but you free to divide the component and the template.
field-button.component.ts
import { Component } from "@angular/core";
import { FieldType } from "@ngx-formly/core";
@Component({
selector: "modal-button",
template: `
<button
[type]="to.type"
[ngClass]="'btn btn-' + to.btnType"
(click)="onClick($event)"
>
{{ to.text }}
</button>
`
})
export class FieldButtonComponent extends FieldType {
onClick($event) {
if (this.to.onClick) {
this.to.onClick($event);
}
}
}
3) Modal input opener
The third ingredient is the input with a button to open our modal. In our example we will create a new input template with a right-floated button to open.
modal-input.component.ts
import { Component } from "@angular/core";
import { FieldType } from "@ngx-formly/core";
@Component({
selector: "modal-input",
template: `
<div class="form-group">
<label [attr.for]="id" class="col-sm-2 col-form-label" *ngIf="to.label">
{{ to.label }}
<ng-container *ngIf="to.required && to.hideRequiredMarker !== true"
>*</ng-container>
</label>
<input
class="col-sm-7"
[type]="type"
[formControl]="formControl"
[formlyAttributes]="field"
[class.is-invalid]="showError"
class="form-control"
/>
<div
*ngIf="to.modable"
class="input-group-addon btn btn btn-primary submit-button"
(click)="onClick($event)">
Open
</div>
<div *ngIf="showError" class="col-sm-3 invalid-feedback d-block">
<formly-validation-message [field]="field"></formly-validation-message>
</div>
</div>
`,
styles: [
`
.form-group {
display: flex;
}
.input-group-addon {
flex-grow: 1;
margin-left: 10px;
padding: 0 1em;
line-height: 28px;
}
`
]
})
export class ModalFieldInputComponent extends FieldType {
defaultOptions = {
templateOptions: {
modable: false
}
};
get type() {
return this.to.type || "text";
}
onClick($event) {
if (this.to.onClick) {
this.to.onClick($event);
}
}
}
Let’s cook them!
Now that we have our wrapper (with accessories) we can insert them into our module, with the Formly import.
my-forms.module.ts
import { NgModule } from '@angular/core';
import {CommonModule} from '@angular/common';
import {FormlyModule} from '@ngx-formly/core';
import {FormlyBootstrapModule} from '@ngx-formly/bootstrap';
import {ReactiveFormsModule} from '@angular/forms';
import {ModalWrapperComponent} from './modal-wrapper.component';
import {FieldButtonComponent} from './field-button.component';
import { ModalFieldInputComponent } from "./modal-input.component";
@NgModule({
declarations: [
FieldButtonComponent,
ModalWrapperComponent,
ModalFieldInputComponent
],
imports: [
CommonModule,
ReactiveFormsModule,
FormlyModule.forRoot({
types: [
{ name: 'button', component: FieldButtonComponent },
{ name: "modal-input", component: ModalFieldInputComponent },
],
wrappers: [
{ name: 'modal', component: ModalWrapperComponent },
],
}),
FormlyBootstrapModule,
]
})
export class FormsModule { }
Then we can mix them together and use our custom new components inside a standard formly form.
my-form.component.html
<form [formGroup]="form" (ngSubmit)="submit()">
<formly-form
[model]="model"
[fields]="fields"
[options]="options"
[form]="form"></formly-form>
<div class="row">
<div class="col-sm-12">
<p class="btn-container">
<button type="submit" class="btn btn-primary"
[disabled]="!form.valid">
Submit
</button>
</p>
</div>
</div>
</form>
{{ model|json }}
my-form.component.ts
import { Component } from "@angular/core";
import { FormGroup } from "@angular/forms";
import { FormlyFormOptions, FormlyFieldConfig } from "@ngx-formly/core";
@Component({
selector: "my-form",
templateUrl: "./app.component.html"
})
export class MyFormComponent {
form = new FormGroup({});
model: any = {};
options: FormlyFormOptions = {};
emailModal = false; // Flag to open/close our modal
fields: FormlyFieldConfig[] = [
// This field uses a custom template to add a little
// button to click and open the modal with the onClick event.
{
key: "modalOpener",
type: "modal-input",
templateOptions: {
modable: true,
disabled: true,
label: "E-mail",
placeholder: "email@email.it",
onClick: $event => {
this.emailModal = true;
}
}
},
/*********** MODAL FIELDS ***********/
{
key: "modalField",
wrappers: ["modal"],
hideExpression: () => !this.emailModal,
templateOptions: {
title: "A modal",
description: "Description of this modal",
onClose: () => {
this.emailModal = false;
}
},
fieldGroup: [
{
key: "email",
type: "input",
templateOptions: {
type: "email",
label: "E-mail",
placeholder: "Insert a valid e-mail",
minLength: 3
}
},
{
key: "myButton",
type: "button",
templateOptions: {
type: "button",
btnType: "primary",
text: "Send",
onClick: $event => {
if (this.form.valid && this.model.modalField.email) {
this.model = {
...this.model,
modalOpener: this.model.modalField.email
};
this.form
.get("modalOpener")
.setValue(this.model.modalField.email);
this.emailModal = false;
console.log("this.model: ", this.model);
}
}
}
}
]
},
{
key: "normal-input",
type: "modal-input",
templateOptions: {
modable: false,
label: "Generic text",
placeholder: "generic value"
}
}
];
submit() {
alert(JSON.stringify(this.model));
}
}
Here is the working example:
That’s all, for creating an Angular Formly form modal.
Try it at home!
Here I am not able to see send button. I simply want on click of button open modal with some form in it.
You are right. I proceeded to insert a “modal-input” template to do this.
I also included a stackblitz example.
Can u plz show this example in stackblitz.