Creating dynamic forms using ngx-formly

Now that we can get to the audit page and get the appointment data, let's set up the form so that we can audit the appointment. Since our appointments can have different units, and we need to inspect different things in each unit type, it would be best to use some type of dynamic form. ngx-formly makes dynamic forms simpler and takes away all the setup with a simple API. By using ngx-formly, you can set up different field types and reuse the template in your dynamic forms throughout the whole application. 

ngx-formly supports different UI libraries, such as Bootstrap, Angular Material, Ionic, NativeScript, Kendo UI, PrimeNG, and others. The configuration for the ngx-formly field can do a lot of different things. You can find various examples at https://ngx-formly.github.io/ngx-formly/examples.

Let's use the schematics for ng add to add ngx-formly to our project. We'll be using ionic as the ui-theme:

> ng add @ngx-formly/schematics --ui-theme=ionic

Since we are using lazy loaded routes, we need to make sure that the reusable modules that are included by the ng add command are added to SharedModule. Let's create a shared module using the following Angular CLI command:

> ng g m shared

Now, we need to remove FormlyModule, Formly IonicModule, and ReactiveFormsModule from AppModule and add them to SharedModule:

import { NgModule } from '@angular/core';
import { CommonModule } from '@angular/common';
import { FormlyModule } from '@ngx-formly/core';
import { FormlyIonicModule } from '@ngx-formly/ionic';
import { ReactiveFormsModule } from
'@angular/forms';

@NgModule({
declarations: [],
imports: [
CommonModule,
ReactiveFormsModule,
FormlyModule.forRoot(),
FormlyIonicModule
],
exports: [
ReactiveFormsModule,
FormlyModule,
FormlyIonicModule
]
})
export class SharedModule { }

By doing this, we can add SharedModule to our page modules, AuditModule and HomeModule.

Now, let's use the formly-form component and pass model, fields, options, and form to it:

<ion-content padding>
<form [formGroup]="form">
<formly-form [model]="model" [fields]="fields" [options]="options"
[form]="form">
</formly-form>
<ion-button shape="round" (click)="submit()" expand="full"
color="primary">
Save
</ion-button>
</form
>
</ion-content>

Let's initialize form as a FormGroup object, model and options as empty objects, and fields as an array with one object. The object type defines what type of field is going to be used. In this case, we want the field to be an input field. The key in the object defines where the value of the field is in the model. The label can be passed to the templateOptions of the field:

...
import
{ FormGroup } from '@angular/forms';
import { FormlyFieldConfig, FormlyFormOptions } from '@ngx-formly/core'
;
...

export class AuditPage implements OnInit {
...
form = new FormGroup({});
options: FormlyFormOptions = {};
model = {};
fields: FormlyFieldConfig[] = [{
type: 'input',
key: 'name',
templateOptions: {

label: 'Name'
}

}];
...
}

Now, you should be able to see one field on the audit page:

Let's replace the console in our valueChanges subscription and dynamically create the fields before assigning them to the fields array and assigning the appointment to the model.

Let's add a function that returns a FormlyFieldConfig array of two items: one that has radio options for the good and bad condition of an item, and another so that the user can state why the item is bad. We will add this to our AuditPage component class, at the top:

const getFields = (id: number, key: string): FormlyFieldConfig[] => {
const label = startCase(key);

return [{
key: `units.${id}.${key}`,
type: 'radio',
templateOptions: {
label,
options: [{
value: 'good', label: 'In Good Condition'
}, {
value: 'bad', label: 'In Bad Condition'
}]
}
}, {
key: `units.${id}.${key}Reason`,
type: 'textarea',
templateOptions: {
label: 'Reason',
placeholder: `Reason for bad condition of ${label}`
}
}];
};

Now, we'll use the getFields function to get all the formly fields configurations. We can do this by using UNIT_KEYS:

...
import
startCase from 'lodash-es/startCase';

const UNIT_KEYS = {
'room': ['lights', 'blinds', 'paint', 'carpet', 'door', 'alarm'],
'kitchen': ['sink', 'stove', 'microwave', 'fridge'],
'bath': ['lights', 'paint', 'floor', 'door', 'knobs'],
'hall': ['lights', 'paint', 'carpet', 'door'],
'patio': ['clean']
};

const getUnitFields = (type: string, id: number): FormlyFieldConfig[] => {
return [
{
template: `<h1>${startCase(type)}</h1>`,
},
...UNIT_KEYS[type].flatMap(unitKey => {
return getFields(id, unitKey);
})
];
}
;

...
class AuditPage implements OnInit {
constructor(
...
) {
...


this.itemRef.valueChanges().subscribe(appointment => {
const fields = appointment.units.map((unit, index) => {
return getUnitFields(unit.type, index);
});
// Flattening the fields array using reduce
this.fields = fields.reduce((acc, e) => acc.concat(e), []);
this.model = appointment
;
});
}
}

We used UNIT_KEYS to get various items, all of which will be checked in each unit type. The getFields method returns two fields for each item to be checked. The first field is a radio field with two options, In Good Condition and In Bad Condition, while the second field is a textarea field so that the user can state their reason for choosing In Bad Condition. Let's also use the number key from the model to show this in the title of the page:

<ion-header>
<ion-toolbar>
<ion-buttons slot="start">
<ion-back-button></ion-back-button>
</ion-buttons>
<ion-title>Audit # {{model.number}}</ion-title>
</ion-toolbar>
</ion-header>
...

Now, we can see that the Reason field for the bad condition shows up, even if we have In Good Condition selected:

Let's make both fields required and use hideExpression to show the Reason field only if the radio field that corresponds to it has a value of bad:

const getFields = (id: number, key: string): FormlyFieldConfig[] => {
...
return [{
key: `units.${id}.${key}`,
type: 'radio',
templateOptions: {
required: true,
...

}
}, {
key: `units.${id}.${key}Reason`,
type: 'textarea',
hideExpression: `model.units[${id}].${key} !== 'bad'`
templateOptions: {
required: true,
...

}
}];
};

Now, when the value of the radio field is good, you won't see the Reason field:

Before we submit the form, we need to update our item with the changes that we made to the model, as well as update its status based on the form's valid status. Then, we will route it to our home page:

import { Router } from '@angular/router';

class AuditPage implements OnInit {

constructor(
...
private router: Router
) {
...
}

...

submit
() {
this.itemRef.update({
...this.model,
status: this.form.invalid ? 'incomplete' : 'complete',
});
this.router.navigateByUrl('/home');
}
}

We have created and completed our first dynamic form using ngx-formly. Now, let's add some Native app functionality to our application so that we can use a camera to capture a photo.

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset
3.134.118.95