Generate Angular ReactiveForms from Swagger/OpenAPI
Tags: angular, openapi, reactiveforms, swagger
Published: 2020-08-01 20:10:30

Angular ReactiveForms, [despite their problems](https://alex-klaus.com/stongly-typed-angular-forms/), are a powerful tool for encoding form validation rules reactively. 

## Single Source of Truth for Validation Rules

Your backend code should be the single source of truth for validation rules. Of course we should validate input in the UI for a better user experience.

Likely we are either implementing the same rules from the same spec, or copying what has been implemented in the API, or layers behind that. We should be asking ourselves, **Where should the single source of truth for validation rules live?** It probably shouldn't be in the Angular app. We can eliminate this manual duplication by generating Angular ReactiveForms from OpenAPI/Swagger specs, rather than hand coding them. 

This eliminates bugs where the validation rules between the UI and API fall out of sync -- or copied incorrectly from the backend to the frontend. When new validation rules change, just rerun the command to generate the reactive form from the updated OpenAPI spec.

This works very well in conjunction with Rest API proxies generated using the [openapi-generator](https://github.com/OpenAPITools/openapi-generator-cli).

## Prerequisite 

If your projects do not meet following prerequisite, you can still use the Quick Start. 

In order for this to work with your Rest API you will need to have your backend provide a well formed Swagger (OpenAPI 2) or OpenAPI 3 spec that includes model-metadata for validation expressed as `type`, `format`, `pattern`, `minLength`, `maxLength`, etc. Frameworks such as SwashbuckleCore and SpringFox (and many others) do this for you based on metadata provided using attributes or annotations.  

## Quick Start

This quick start uses a hosted swagger spec, so if you can still go through it whether or not your API exposes the required model-metadata.

First, let's create a new app. 
```bash
npm i -g @angular/cli
ng n example
cd example
```

Second, install the generator into your Angular project as a dev dependancy. 
```bash
npm install --save-dev @verizonconnect/ngx-form-generator
```
Third, update your `package.json` `scripts` to include a script to generate the form. This way when the API changes we can easily rerun this script to regenerate the form. 

```json
{ 
 . . . 
  "scripts": { 
  . . .
    "generate:address-form": "ngx-form-generator -i https://raw.githubusercontent.com/verizonconnect/ngx-form-generator/master/demo/swagger.json -o src/app/"
  },
  . . .
}
```

Now run the script. 
```bash
npm run generate:address-form
```

Within your `src/app` you will now have a new generated file based on the name of the OpenAPI `title` property, in this case `myApi.ts`. You can change this using the `-f` argument and provide the filename you like. 

We can now import the form into a component and expose it to the template. 

```typescript
import { Component } from '@angular/core';
import { addressModelForm } from './myApi'; // <- import the form

@Component({
  selector: 'app-root',
  templateUrl: './app.component.html',
  styleUrls: ['./app.component.scss']
})
export class AppComponent {
  addressForm = addressModelForm; // <- expose form to template
}
```
We will add Angular Material to our project to provide styles for our form in this example. Of course there is no dependency on any CSS or component library.

```bash
ng add @angular/material
```

In the module lets import `ReactiveFormModule`, `MatFormFieldModule` and `MatInputModule`. 


```typescript
import { BrowserModule } from '@angular/platform-browser';
import { NgModule } from '@angular/core';
import { AppComponent } from './app.component';
import { BrowserAnimationsModule } from '@angular/platform-browser/animations';
import { ReactiveFormsModule } from '@angular/forms'; // <- ESM imports start
import { MatFormFieldModule } from '@angular/material/form-field';
import { MatInputModule } from '@angular/material/input';

@NgModule({
  declarations: [
    AppComponent
  ],
  imports: [
    BrowserModule,
    BrowserAnimationsModule,
    ReactiveFormsModule, // <- NgModule imports start
    MatFormFieldModule,
    MatInputModule
  ],
  providers: [],
  bootstrap: [AppComponent]
})
export class AppModule { }
```
We will build the form. For demonstration purposes only need the first field. 

```html
<form [formGroup]="addressForm">

  <mat-form-field>
    <mat-label>First Name</mat-label>
    <input matInput formControlName="firstName">
    <mat-error *ngIf="addressForm.controls.firstName.invalid">This field is invalid</mat-error>
  </mat-form-field>

</form>
```

We can now run this with `ng serve` and see our single field form. 

![Empty Field](https://dev-to-uploads.s3.amazonaws.com/i/3nue2w72wrixcmakfioe.png)

An invalid name can be entered into the field and we will see this validated based on the validation rules from exposed to the Angular application through the OpenAPI spec. 

![Invalid Field](https://dev-to-uploads.s3.amazonaws.com/i/ktaodi35qnu9hftl1lcw.png)

Here we can enter a valid name and see that the validation updates. 

![Valid Field](https://dev-to-uploads.s3.amazonaws.com/i/r1vc9m68v55jail5cqp0.png)

If this were a real RestAPI we could add the rest of our form fields and go. 

## Isolate Generated Code into a Library

We can improve this by putting the generated form into its own library within the Angular workspace. The benefits of this are: 

1. Clear separation of boundaries between the generated and crafted code. One of the power-dynamics of generating API proxies and forms is being able to safely regenerate them. This will help prevent a team-member from manually modifying the generated form.  
2. No need to recompile the form during local development. The form project will only need to be recompiled when after it has been regenerated. 
3. We can add this generation process as part of the CICD build process. 

Create a new library
```bash
ng g lib address-form
```
We can now remove the scaffolded component, service and module from the lib. 

```bash
rm -fr projects/address-form/src/lib/* 
``` 

We are placing only generated code into this library. We do not want to create unit tests for generated code. Tests should live with the code generator itself. So lets get rid of the unit test support files. 

```bash
rm projects/address-form/karma.conf.js
rm projects/address-form/tsconfig.spec.json
rm projects/address-form/src/test.ts
```

We don't need to lint generated code, so lets get rid of tslint.json

```bash
rm projects/address-form/tslint.json
```

We need to remove the references to the test and lint files in the workspace `angular.json`. Open `angular.json` and find `"address-form": {` property. Remove the `"test": {` and `"lint": {` sections. 

It should then look something like this. 

```json
    "address-form": {
      "projectType": "library",
      "root": "projects/address-form",
      "sourceRoot": "projects/address-form/src",
      "prefix": "lib",
      "architect": {
        "build": {
          "builder": "@angular-devkit/build-ng-packagr:build",
          "options": {
            "tsConfig": "projects/address-form/tsconfig.lib.json",
            "project": "projects/address-form/ng-package.json"
          },
          "configurations": {
            "production": {
              "tsConfig": "projects/address-form/tsconfig.lib.prod.json"
            }
          }
        }
      }
    }
```

In `package.json` we need to add a script to build our lib as well as update the path where we generate the form. 

```json
    "generate:address-form": "ngx-form-generator -i https://raw.githubusercontent.com/verizonconnect/ngx-form-generator/master/demo/swagger.json -o projects/address-form/src/lib",
    "build:libs": "ng build address-form"
```

Now lets generate the form into the lib. 

```bash
npm run generate:address-form
```

Now replace all of the exports in `proects/address-form/src/public-api.ts` with: 

```typescript
export * from './lib/myApi';
```

Build the lib with: 
```bash
npm run build:libs
``` 

We can remove the old generated `myApi.ts`

```bash
rm src/app/myApi.ts
```
Last, update the import of the form 

```typescript
import { addressModelForm } from 'address-form';
```

## Conclusion 

Generating forms will allow you to keep your validation rules in sync with the backend. Like generating proxies, this will greatly reduce integration bugs that occur by trying to manually implement backend to UI data contracts.