npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2024 – Pkg Stats / Ryan Hefner

@codexio/ngx-reactive-forms-generator

v1.0.3

Published

Creates Angular FormGroup instances from DTOs (e.g. class PersonRequest) using decorators

Downloads

13

Readme

ngx-reactive-forms-generator

Creates Angular FormGroup instances from DTOs (e.g. class PersonRequest) using decorators

Table of Contents

Preambule^

Have you felt the burden of describing your models in classes/interfaces and then redescribing them as FormGroup objects with multiple FormControl nested children? All the boilerplate that you need to write in order to achieve a decent Reactive Forms foundation in your project? It's not the case anymore!

With several TypeScript decorators called @FormGroupTarget, @FormGroupValidators, @FormGroupAsyncValidators, @FormControlTarget, @FormControlAsyncValidators and @NestedFormGroup you can have a Reactive Form right out of your DTO.

Forget having this repetition and boilerplate:

interface LoginForm {
  email: FormControl<string>;
  password?: FormControl<string>;
}

const loginFormGroup = new FormGroup<LoginForm>({
  email: new FormControl('', {nonNullable: true}),
  password: new FormControl('', Validators.minLength(6)),
});

But instead welcome:

class LoginForm {
  @FormControlTarget({nonNullable: true})
  email: string;
  
  @FormControlTarget(Validators.minLength(6))
  password: string;
}

const loginFormGroup = toFormGroup<LoginForm>(LoginForm)!;

API Docs with Examples^

Decorator @FormGroupTarget^

Decorator used on a class to denote that this class will be a target type if a form group value.

If your class will be a target of multiple forms, which have different options, validators, and maybe subset of fields, you can use this decorator multiple times, denoting a unique identifier called formId. Then each validator and form control which is participating in the particular form group should share the same formId

Usage:

@FormGroupTarget()
@FormGroupTarget("editForm")
class InvoiceRequest {

}

Decorator @FormGroupValidators^

Decorator used on a class to denote that this class, which is already a target of a form group (@FormGroupTarget) will have synchronous validators.

In order to add different validators to different form groups of the same type, an additional parameter formId should be supplied

Example:

@FormGroupTarget()
@FormGroupValidators(Validators.email)

@FormGroupTarget("editForm")
@FormGroupValidators(Validators.requiredTrue, "editForm")
class InvoiceRequest {

}

If two or more forms share the same validators, an array of formId could be supplied, so they will reflect in all of them. If you do not name your default form group, a formId will be assigned from the library. In order to access it later (e.g. in the formId array) use the DEFAULT_GROUP constant.

Example:

@FormGroupTarget()
@FormGroupTarget("editForm")
@FormGroupValidators([Validators.email, Validators.requiredTrue], [DEFAULT_GROUP, "editForm"])
class InvoiceRequest {

}
    

Decorator @FormGroupAsyncValidators^

Decorator used on a class to denote that this class, which is already a target of a form group (@FormGroupTarget) will have asynchronous validators.

In order to add different validators to different form groups of the same type, an additional parameter formId should be supplied

Example:

@FormGroupTarget()
@FormGroupValidators(Validators.email)
@FormGroupAsyncValidators(ctrl => of({ value: ctrl.value}))

@FormGroupTarget("editForm")
@FormGroupValidators(Validators.requiredTrue, "editForm")
@FormGroupAsyncValidators(ctrl => of({ empty: !ctrl.value}))
class InvoiceRequest {

}

If two or more forms share the same validators, an array of formId could be supplied, so they will reflect in all of them. If you do not name your default form group, a formId will be assigned from the library. In order to access it later (e.g. in the formId array) use the DEFAULT_GROUP constant.

Example:

@FormGroupTarget()
@FormGroupTarget("editForm")
@FormGroupValidators([Validators.email, Validators.requiredTrue], [DEFAULT_GROUP, "editForm"])
@FormGroupAsyncValidators([ctrl => of({ value: ctrl.value}), ctrl => of({ empty: !ctrl.value})], [DEFAULT_GROUP, "editForm"])
class InvoiceRequest {

}

Decorator @FormControlTarget^

Decorator used on a field/property or a constructor parameter to denote that this property is a FormControl

Same as @FormGroupValidators it can be associated to a particular form group by supplying formId.

This decorator also accepts as an argument on or more Validators or FormControlOptions (optionally).

Example:

@FormGroupTarget()
@FormGroupValidators(Validators.email)
@FormGroupAsyncValidators(ctrl => of({ value: ctrl.value}))

@FormGroupTarget("editForm")
@FormGroupValidators(Validators.requiredTrue, "editForm")
@FormGroupAsyncValidators(ctrl => of({ empty: !ctrl.value}))
class InvoiceRequest {

  @FormControlTarget(Validators.required)
  @FormControlTarget(Validators.minLength(3), "editForm")
  public num: string = '001';

  constructor(
      @FormControlTarget([], "editForm")
      public date: Date = new Date()
  ) { }
}

If a form control shares the same settings on two or more form groups, an array of formId can be supplied

Example:

@FormGroupTarget()
@FormGroupValidators(Validators.email)
@FormGroupAsyncValidators(ctrl => of({ value: ctrl.value}))

@FormGroupTarget("editForm")
@FormGroupValidators(Validators.requiredTrue, "editForm")
@FormGroupAsyncValidators(ctrl => of({ empty: !ctrl.value}))
class InvoiceRequest {

  @FormControlTarget([Validators.required, Validators.minLength(3)], [DEFAULT_GROUP, "editForm"])
  public num: string = '001';

  constructor(
      @FormControlTarget([], [DEFAULT_GROUP, "editForm"])
      public date: Date = new Date()
  ) { }
}

Decorator @FormControlAsyncValidators^

Decorator used on a field/property or a constructor parameter to denote that this form control has asynchronous validators.

Same as @FormGroupValidators it can be associated to a particular form group by supplying formId.

Example:

@FormGroupTarget()
@FormGroupValidators(Validators.email)
@FormGroupAsyncValidators(ctrl => of({ value: ctrl.value}))

@FormGroupTarget("editForm")
@FormGroupValidators(Validators.requiredTrue, "editForm")
@FormGroupAsyncValidators(ctrl => of({ empty: !ctrl.value}))
class InvoiceRequest {

  @FormControlTarget(Validators.required)
  @FormControlTarget(Validators.minLength(3), "editForm")
  @FormControlAsyncValidators(ctrl => of({ ok: ctrl.state == 'VALID'}))
  @FormControlAsyncValidators(ctrl => of({ notOk: ctrl.state != 'VALID'}), "editForm")
  public num: string = '001';

  constructor(
      @FormControlTarget([], "editForm")
      @FormControlAsyncValidators(ctrl => of({ ok: ctrl.state == 'VALID'}))
      public date: Date = new Date()
  ) { }
}

If a form control shares the same async validators on two or more form groups, an array of formId can be supplied

Example:

@FormGroupTarget()
@FormGroupValidators(Validators.email)
@FormGroupAsyncValidators(ctrl => of({ value: ctrl.value}))

@FormGroupTarget("editForm")
@FormGroupValidators(Validators.requiredTrue, "editForm")
@FormGroupAsyncValidators(ctrl => of({ empty: !ctrl.value}))
class InvoiceRequest {

  @FormControlTarget([Validators.required, Validators.minLength(3)], [DEFAULT_GROUP, "editForm"])
  @FormControlAsyncValidators([ctrl => of({ ok: ctrl.state == 'VALID'}), ctrl => of({ notOk: ctrl.state != 'VALID'})], [DEFAULT_GROUP, "editForm"])
  public num: string = '001';

  constructor(
      @FormControlTarget([], [DEFAULT_GROUP, "editForm"])
      @FormControlAsyncValidators(ctrl => of({ ok: ctrl.state == 'VALID'}), [DEFAULT_GROUP, "editForm"])
      public date: Date = new Date()
  ) { }
}

Decorator @NestedFormGroup^

Decorator used on a field/property or a constructor parameter to denote that this form control is actually a previously defined form group, in other words - a nested form group.

This is the equivalent of:

const topLevelFormGroup = new FormGroup({
 fieldOne: new FormControl(...),
 nestedFormGroup: new FormGroup({  // <---- this 
     nestedFieldInNestedGroup: new FormControl(...)
 })
});

Same as FormGroupValidators it can be associated to a particular form group by supplied targetFormId

Also, since the nested entity may define more than one form group as described the nature of the decorators above, the second parameter sourceFormId actually chooses which of them to nest.

Example (Basic):

@FormGroupTarget()
class SupplierRequest {

 @FormControlTarget()
 public name: string = '';

 @FormControlTarget()
 public address: string = '';
}

@FormGroupTarget()
@FormGroupValidators(Validators.email)
@FormGroupAsyncValidators(ctrl => of({ value: ctrl.value}))

@FormGroupTarget("editForm")
@FormGroupValidators(Validators.requiredTrue, "editForm")
@FormGroupAsyncValidators(ctrl => of({ empty: !ctrl.value}))
class InvoiceRequest {

@FormControlTarget(Validators.required)
@FormControlTarget(Validators.minLength(3), "editForm")
@FormControlAsyncValidators(ctrl => of({ ok: ctrl.state == 'VALID'}))
@FormControlAsyncValidators(ctrl => of({ notOk: ctrl.state != 'VALID'}), "editForm")
public num: string = '001';

constructor(
   @FormControlTarget([], "editForm")
   @FormControlAsyncValidators(ctrl => of({ ok: ctrl.state == 'VALID'}))
   public date: Date = new Date(),

   @NestedFormGroup(SupplierRequest)
   public supplier: SupplierRequest = new SupplierRequest()
) { }
}

If the nested form group (the SupplierRequest) defines more than one form group, we can choose which one of them to nest.

Example:

@FormGroupTarget()
@FormGroupTarget("validatedSupplier")
class SupplierRequest {

 @FormControlTarget()
 @FormControlTarget(Validators.required, "validatedSupplier")
 public name: string = '';

 @FormControlTarget()
 @FormControlTarget(Validators.required, "validatedSupplier")
 public address: string = '';
}

@FormGroupTarget()
@FormGroupValidators(Validators.email)
@FormGroupAsyncValidators(ctrl => of({ value: ctrl.value}))

@FormGroupTarget("editForm")
@FormGroupValidators(Validators.requiredTrue, "editForm")
@FormGroupAsyncValidators(ctrl => of({ empty: !ctrl.value}))
class InvoiceRequest {

@FormControlTarget(Validators.required)
@FormControlTarget(Validators.minLength(3), "editForm")
@FormControlAsyncValidators(ctrl => of({ ok: ctrl.state == 'VALID'}))
@FormControlAsyncValidators(ctrl => of({ notOk: ctrl.state != 'VALID'}), "editForm")
public num: string = '001';

constructor(
   @FormControlTarget([], "editForm")
   @FormControlAsyncValidators(ctrl => of({ ok: ctrl.state == 'VALID'}))
   public date: Date = new Date(),

   @NestedFormGroup(SupplierRequest, "validatedSupplier")
   public supplier: SupplierRequest = new SupplierRequest()
) { }
}

In the previous example we have nested the validatedSupplier form group to the default form group of InvoiceRequest. We can choose to nest it to editForm or to all of them

Example:

@FormGroupTarget()
@FormGroupTarget("validatedSupplier")
class SupplierRequest {

 @FormControlTarget()
 @FormControlTarget(Validators.required, "validatedSupplier")
 public name: string = '';

 @FormControlTarget()
 @FormControlTarget(Validators.required, "validatedSupplier")
 public address: string = '';
}

@FormGroupTarget()
@FormGroupValidators(Validators.email)
@FormGroupAsyncValidators(ctrl => of({ value: ctrl.value}))

@FormGroupTarget("editForm")
@FormGroupValidators(Validators.requiredTrue, "editForm")
@FormGroupAsyncValidators(ctrl => of({ empty: !ctrl.value}))
class InvoiceRequest {

@FormControlTarget(Validators.required)
@FormControlTarget(Validators.minLength(3), "editForm")
@FormControlAsyncValidators(ctrl => of({ ok: ctrl.state == 'VALID'}))
@FormControlAsyncValidators(ctrl => of({ notOk: ctrl.state != 'VALID'}), "editForm")
public num: string = '001';

constructor(
   @FormControlTarget([], "editForm")
   @FormControlAsyncValidators(ctrl => of({ ok: ctrl.state == 'VALID'}))
   public date: Date = new Date(),

   @NestedFormGroup(SupplierRequest, "validatedSupplier", [DEFAULT_GROUP, "editForm"])
   public supplier: SupplierRequest = new SupplierRequest()
) { }
}

Function toFormGroup<Type>(Type, [FormIdType])^

This function returns a FormGroup (in particular an enhanced ModelFormGroup which value< property will be of a generic type supplied) based on the decorators upon a class.

Example:

ngOnInit(): void {
    const formGroup = toFormGroup<InvoiceRequest>(InvoiceRequest);
    const invoice: InvoiceRequest = formGroup.value!;
}

It accepts an optional second parameter formId if you want to retrieve a particular (non-default) form.

Example

ngOnInit(): void {
    const defaultFormGroup = toFormGroup<InvoiceRequest>(InvoiceRequest);
    const defaultInvoice: InvoiceRequest = defaultFormGroup.value!;
    
    const editFormGroup = toFormGroup<InvoiceRequest>(InvoiceRequest, "editForm");
    const editInvoice: InvoiceRequest = editFormGroup.value!;
}

Function toFormGroups<Type>(Type, FormIdType[])^

Same as toFromGroup, but gives an array of form groups associated to this Type depending on the array of formId supplied. If empty array is supplied, it will return all form groups associated to the given Type

Example:

ngOnInit(): void {
    const allFormGroupsImplicit = toFormGroups<InvoiceRequest>(InvoiceRequest);
    allFormGroupsImplicit.forEach(invoiceFormGroup => {
        const invoice: InvoiceRequest = invoiceFormGroup.value!;
    });
    
    const allFormGroupsExplicit = toFormGroups<InvoiceRequest>(InvoiceRequest, [DEFAULT_GROUP, "editForm"]);
    allFormGroupsExplicit.forEach(invoiceFormGroup => {
        const invoice: InvoiceRequest = invoiceFormGroup.value!;
    });
}

Installation^

Installation is very simple. You just need to install the npm package @codexio/ngx-reactive-forms-generator into your Angular project. Then all the above decorators and functions will be available to be imported across your project

Installation / Usage Example^

Terminal

$ npm i @codexio/ngx-reactive-forms-generator

app.component.ts

import {FormControlTarget, ModelFormGroup, toFormGroup} from "@codexio/ngx-reactive-forms-generator";

import {Component} from '@angular/core';
import {Validators} from "@angular/forms";

class LoginForm {
  @FormControlTarget({updateOn: 'submit', validators: Validators.required})
  email: string = '';

  @FormControlTarget(Validators.minLength(6))
  password: string = '';
}

@Component({
  selector: 'app-root',
  templateUrl: './app.component.html'
})
export class AppComponent implements OnInit {

  loginForm: ModelFormGroup<LoginForm> = toFormGroup<LoginForm>(LoginForm)!;

  login() {
    console.log(this.loginForm);
    console.log(this.loginForm.value); // LoginForm object
  }
  
}

app.component.html (Not related to this library, it uses standard Reactive Forms notations):

<h1>Login</h1>
<form [formGroup]="loginForm" (ngSubmit)="login()">
  <input type="text" formControlName="email" /><br/>
  <div *ngIf="loginForm.get('email')?.errors?.['required'] && loginForm.get('email')?.dirty">
    Email is required <!-- Will appear after submit -->
  </div>

  <input type="password" formControlName="password" /><br/>
  <div *ngIf="loginForm.get('password')?.errors?.['minlength'] && loginForm.get('password')?.dirty">
    Password too short <!-- Will appear when you change the value and it's with length < 6 -->
  </div>

  <button type="submit">Login</button>
</form>

Enjoy :)