Nanoseconds Produce Minutes
    Have ideas to improve npm?Join in the discussion! »

    angular-typesafe-reactive-forms-helper
    TypeScript icon, indicating that this package has built-in type declarations

    2.0.2 • Public • Published

    angular-typesafe-reactive-forms-helper

    GitHub Workflow Status (branch) GitHub package.json version GitHub commit activity GitHub npm GitHub forks

    Quick Syntax

    Instead of:

    this.form.get('heroName').patchValue('He-Man');

    angular-typesafe-reactive-forms-helper allows:

    this.form.getSafe(x => x.heroName).patchValue('He-Man');

    Why

    • Get intellisense
    • No more misspelled property names
    • Refactoring Reactive Forms is back to a trivial IDE rename task

    Demo

    In order to make this work as closely as possible to the Angular way, an abstract class FormGroupTypeSafe<T> was derived from Angular’s FormGroup with the intent not to break existing code.

    Intellisense on FormGroupTypeSafe.value:

    FormGroupTypeSafe.value Intellisense

    Intellisense on FormGroupTypeSafe.getSafe and then patching the value:

    FormGroupTypeSafe.getSafe Intellisense

    How to use:

    1. Define an interface of your form model.

    //interface used with FormGroupTypeSafe<T>
    interface IHeroFormModel {
      name: string;
      secretLairs: Array<Address>;
      power: string;
      sidekick: string
    }

    2. Declare your new FormGroupTypeSafe form with the help of TypeScript’s generics.

    /* TypeSafe Reactive Forms Changes */
    //old code
    //heroForm: FormGroup;
    heroForm: FormGroupTypeSafe<IHeroFormModel>;

    3. Inject FormBuilderTypeSafe

    constructor(
       /* TypeSafe Reactive Forms Changes */
       //old code - private fb: FormBuilder,
       private fb: FormBuilderTypeSafe,
       private heroService: HeroService) {
    
       this.createForm();
       this.logNameChange();
     }

    4. Create your form group with Interfaces (contracts).

    // old code
    //    this.heroForm = this.fb.group({
    //      name: '',
    //      secretLairs: this.fb.array([]),
    //      power: '',
    //      sidekick: ''
    //    });
    
    
     this.heroForm = this.fb.group<IHeroFormModel>({
          name: new FormControl(''),
          secretLairs: new FormControl([]),
          power: new FormControl(''),
          sidekick: new FormControl('')
        });
    
    //***** Nested type sample *****
    interface IAddressModel {
       suburb: string;
       postcode: string;
    }
    
    interface ICustomerModel {
      name: string;
      address: IAddressModel;
    }
    
     this.form = this.fb.group<ICustomerModel>({
            name: new FormControl(null, [Validators.required]),
            address: this.formBuilder.group<IAddressModel>({
                suburb: new FormControl(''),
                postcode: new FormControl('', [Validators.required]),
          })
      });

    Peer Dependencies

    @angular/forms and all its peer dependencies.

    This package has been tested with Angular 9, 10, 11.

    (Should work with Angular 4, 5, 6, 7, 8 too)

    I would encourage you to use versions Angular still support, see Angular's Support policy and schedule.

    Blog

    For a more in detail description of the benefits of this package, read my blog - Angular typesafe reactive forms.

    When reading the blog, be mindful that it was written Oct-2017, before the angular-typesafe-reactive-forms-helper package existed. Back then, the idea was to copy the code and adjust as needed. Since then, there were a few requests, thus angular-typesafe-reactive-forms-helper was born.

    Contributions

    I only added features required by my projects, but I know more could be added with your help.

    Create a PR to get the conversation started 😄

    Lastly

    Use it…don’t use it 😄


    Release notes

    The model used for all code samples:

    interface HeroFormModel {
        heroName: string;
        weapons: WeaponModel[];
    }
      
    interface WeaponModel {
        name: string;
        damagePoints: number;
    }

    FormGroupTypeSafe<T> extends Angular's FormGroup class

    V2.0.2 (2021-05-18)

    • Bump to Angular 11.
    • Stop integration tests for Angular 8.

    V2.0.1 (2020-12-09)

    Package the correct library files, instead of the repository - rookie mistake :)

    V2.0.0 (2020-11-06)

    • use ng-packagr to fix bug - main.ts:15 Error: Angular JIT compilation failed
    • add end-to-end-tests Angular 8, 9, 10
    • removed Angular 7 integration tests from build pipeline as it is no longer supported by Angular team

    New dist file structure:

    ./dist:
    LICENSE
    README.md
    angular-typesafe-reactive-forms-helper.d.ts
    angular-typesafe-reactive-forms-helper.metadata.json
    bundles
    esm2015
    fesm2015
    package.json
    public_api.d.ts
    src
    
    ./dist/bundles:
    angular-typesafe-reactive-forms-helper.umd.js
    angular-typesafe-reactive-forms-helper.umd.js.map
    angular-typesafe-reactive-forms-helper.umd.min.js
    angular-typesafe-reactive-forms-helper.umd.min.js.map
    
    ./dist/esm2015:
    angular-typesafe-reactive-forms-helper.js
    public_api.js
    src
    
    ./dist/esm2015/src:
    angularTypesafeReactiveFormsHelper.js
    getPropertyName.js
    
    ./dist/fesm2015:
    angular-typesafe-reactive-forms-helper.js
    angular-typesafe-reactive-forms-helper.js.map
    
    ./dist/src:
    angularTypesafeReactiveFormsHelper.d.ts
    getPropertyName.d.ts
    

    Old dist file structure:

    ./dist:
    LICENSE
    README.md
    lib
    package.json
    
    ./lib:
    angularTypesafeReactiveFormsHelper.d.ts
    angularTypesafeReactiveFormsHelper.js
    getPropertyName.d.ts
    getPropertyName.js
    

    V1.8.2 (2020-09-04)

    • Fix bug - getSafe() call fails and returns null when compiled to ES5.

    V1.8.1 (2020-06-26)

    • Bump to Angular 10.
    • Stop integration tests for Angular 6.

    V1.8.0 (2020-06-16)

    • added removeControlSafe

    Sample:

     let sut: FormGroupTypeSafe<HeroFormModel> = createGroup();
     sut.removeControlSafe(x => x.heroName);

    The bottom code was avoided simply because in a variable rename scenario, the IDE should rename all the references instead of just informing one where the errors are.

    removeControl(name: keyof T): void;
    removeControl(name: string): void;

    V1.7.0 (2020-05-14)

    • added controls

    Angular's forms.d.ts:

    controls: { [key: string]: AbstractControl; };

    angular-typesafe-reactive-forms-helper:

    controls: { [P in keyof T]: AbstractControlTypeSafe<T[P]> };

    Code samples:

    let sut: FormGroupTypeSafe<HeroFormModel> = createGroup();
    // $ExpectType { heroName: AbstractControlTypeSafe<string>; weapons: AbstractControlTypeSafe<WeaponModel[]>; }
     sut.controls;
    
     // $ExpectType AbstractControlTypeSafe<string>
     sut.controls.heroName;
     // $ExpectType AbstractControlTypeSafe<WeaponModel[]>
     sut.controls.weapons;
    
     // $ExpectType string
     sut.controls.heroName.value;
     // $ExpectType WeaponModel[]
     sut.controls.weapons.value;

    V1.6.0 (2020-04-22)

    • added statusChanges and status

    Angular's forms.d.ts:

    /**
     * The validation status of the control. There are four possible
     * validation status values:
     *
     * * **VALID**: This control has passed all validation checks.
     * * **INVALID**: This control has failed at least one validation check.
     * * **PENDING**: This control is in the midst of conducting a validation check.
     * * **DISABLED**: This control is exempt from validation checks.
     *
     * These status values are mutually exclusive, so a control cannot be
     * both valid AND invalid or invalid AND disabled.
     */
    readonly status: string;

    angular-typesafe-reactive-forms-helper:

     export type ControlStatus = 'VALID' | 'INVALID' | 'PENDING' | 'DISABLED';
    
     export interface FormGroupTypeSafe<T> extends FormGroup {
      readonly status: ControlStatus;
      readonly statusChanges: Observable<ControlStatus>;
    }

    Code samples:

     let sut: FormGroupTypeSafe<HeroFormModel> = createGroup();
    
     // $ExpectType ControlStatus
     sut.status;
    
     sut.statusChanges.subscribe(val => {
         // $ExpectType ControlStatus
         val;
     });
    
     // $ExpectType string | undefined
     sut.getSafe(x => x.heroName)?.status; // unfortunately this is still string ¯\_(ツ)_/¯
    
     sut.getSafe(x => x.heroName)?.statusChanges.subscribe(val => {
         // $ExpectType ControlStatus
         val;
     });
     

    V1.5.1 (2020-04-17)

    Had this error in Angular 9.1.2 when executing ng serve. The app would show a blank page with an error in browser's devtools console:

    main.ts:15 Error: Angular JIT compilation failed: '@angular/compiler' not loaded!
      - JIT compilation is discouraged for production use-cases! Consider AOT mode instead.
      - Did you bootstrap using '@angular/platform-browser-dynamic' or '@angular/platform-server'?
      - Alternatively provide the compiler with 'import "@angular/compiler";' before bootstrapping.
        at getCompilerFacade (core.js:643)
        at Function.get (core.js:16349)
        at getFactoryDef (core.js:2200)
        at providerToFactory (core.js:17183)
        at providerToRecord (core.js:17165)
        at R3Injector.processProvider (core.js:16981)
        at core.js:16960
        at core.js:1400
        at Array.forEach (<anonymous>)
        at deepForEach (core.js:1400)
    

    This is fixed.

    More info on the error from StackOverflow.


    V1.5.0 (2020-04-15)

    Extend AbstractControlTypeSafe<P> with:

      readonly valueChanges: Observable<T>;
      get(path: Array<string> | string): AbstractControl | null; 
      get(path: number[]): AbstractControlTypeSafe<T extends (infer R)[] ? R : T> | null;
    • Samples readonly valueChanges: Observable<T>;:
    let sut: FormGroupTypeSafe<HeroFormModel> = createGroup();
    sut.valueChanges.subscribe(val => {
        // $ExpectType HeroFormModel
        val;
    });
    
    sut.getSafe(x => x.heroName).valueChanges.subscribe(val => {
        // $ExpectType string
        val;
    });
    • Split Angular's get into two functions based on the path: Array<string | number> | string parameter.

    Angular's forms.d.ts:

          get(path: Array<string | number> | string): AbstractControl | null;

    angular-typesafe-reactive-forms-helper:

     get(path: Array<string> | string): AbstractControl | null;
     get(path: number[]): AbstractControlTypeSafe<T extends (infer R)[] ? R : T> | null;

    This allows type safety when working with arrays.

    sut.getSafe(x => x.weapons).get([0]).valueChanges.subscribe(val => {
        // $ExpectType WeaponModel
        val;
    });
    
    // the angular way - .get('person.name')
    sut.getSafe(x => x.weapons).get('person.name').valueChanges.subscribe(val => {
        // $ExpectType any
        val;
    });
    
    // the angular way - .get(['person', 'name'])
    sut.getSafe(x => x.weapons).get(['person', 'name']).valueChanges.subscribe(val => {
        // $ExpectType any
        val;
    });

    V1.4.0 (2020-04-14)

    • new interface AbstractControlTypeSafe<P> which extends from Angular's AbstractControl and will, over time, contain the common properties to Angular's FormGroup, FormControl and FormArray. Currently it only returns readonly value: T.

    • enhanced getSafe to return AbstractControlTypeSafe<P>

      getSafe<P>(propertyFunction: (typeVal: T) => P): AbstractControlTypeSafe<P> | null;

    Code example:

     // heroName: string
     sut.getSafe(x => x.heroName)?.value; // value's ExpectType => string | undefined
    • add new type RecursivePartial<T>
    • enhanced patchValue to use RecursivePartial<T> so one is not forced by the compiler to complete mandatory properties on a nested types.
    patchValue(value: RecursivePartial<T>, options?: Object): void;

    Code Example:

    let sut: FormGroupTypeSafe<HeroFormModel> = formBuilderTypeSafe.group<HeroFormModel>({...}) // let's pretend a valid FormGroupTypeSafe object was created here  
    // Looking at the line below... 
    // Before V1.4.0, Typescript would have complained about missing property damagePoints.
    // This is not the case anymore as now all nested types will be Partial properties. 
    sut.patchValue({ weapons: [{ name: "Head" }]});

    V1.3.0 (2020-04-06)

    • patchValue

    Angular's forms.d.ts:

    patchValue(value: any, options?: Object): void;

    angular-typesafe-reactive-forms-helper:

    patchValue(value: Partial<T>, options?: Object): void;
    • formBuilderTypeSafe.group<T> supports FormArray
     sut = formBuilderTypeSafe.group<HeroFormModel>({
          heroName: new FormControl('He-Man', Validators.required),
          weapons: new FormArray([formBuilderTypeSafe.group<WeaponModel>({
                name: new FormControl('Sword', Validators.required),
                damagePoints: new FormControl(50, Validators.required)
            }),
            formBuilderTypeSafe.group<WeaponModel>({
                name: new FormControl('Shield', Validators.required),
                damagePoints: new FormControl(0, Validators.required)
            }),
          ])
        });

    V1.2.0 (2020-04-02)

    • valueChanges, function returns Observable<T>

    Angular's forms.d.ts:

    valueChanges: Observable<any>;

    angular-typesafe-reactive-forms-helper:

    valueChanges: Observable<T>;

    V1.1.0 (2020-03-31)

    • setValue, just a function signature update.

    Angular's forms.d.ts function signature:

        setValue(value: {
            [key: string]: any;
        }, options?: {
            onlySelf?: boolean;
            emitEvent?: boolean;
        }): void;

    angular-typesafe-reactive-forms-helper signature:

        setValue(value: T, 
                options?: { 
                  onlySelf?: boolean; 
                  emitEvent?: boolean 
        }): void;

    V1.0.0 (2020-03-29)

    angular-typesafe-reactive-forms-helper has these extra functions:

    • getSafe
    • setControlSafe

    Install

    npm i angular-typesafe-reactive-forms-helper

    DownloadsWeekly Downloads

    231

    Version

    2.0.2

    License

    MIT

    Unpacked Size

    123 kB

    Total Files

    18

    Last publish

    Collaborators

    • avatar