Alex KlausFull Stack Developer  |  Architect  |  Scrum Master
Make Angular Reactive Forms strongly typed
13 April 2019

Make Angular Reactive Forms strongly typed

In Angular, the Reactive Forms provide all the power of rxjs/Observable for event handling, sophisticated validators and allow unit testing.

But anyone who steps on the path of leveraging the Reactive Forms quickly notices the elephant in the room – they are NOT strongly typed! Shocker. A quick search reveals that requests to make the Reactive Forms strongly typed are almost as old as Angular 2+ (see issues #13721 and #17000). There is a 1.5 year old pull request, but it still open and no movements on the horizon.

What we need from strongly typed forms?

My requirements for a solution to make Reactive Forms strongly typed are:

  1. Events and properties of the specified type of the form.

    Best candidates are valueChanges event and form.value property, which should be of type Observable<TClass> and TClass respectively.

  2. Ability to generate a basic form group from a class/interface type or its instance.

    E.g. FormBuilder.group<TClass>() or FormBuilder.group(myClassInstance)

  3. Low maintenance and alignment with future Angular versions (no or minimum adjustment for next Angular versions).

Solution 1. TypeScript declaration file

The most elegant solution I’ve seen was provided by Daniele Morosinotto, who suggested leveraging TypeScript declaration files (*.d.ts) to introduce generic interfaces extending the standard ones (e.g. AbstractControl, FormControl, etc.). It doesn’t introduce any new functionality and has no footprint in the compiled JavaScript, but at the same time enforcing strong type checking. Brilliant!

Adopting the solution is straightforward:

  1. Download TypedForms.d.ts from his gist and save it as src/typings.d.ts in your project (Angular 6+ already knows how to use this file, see more in the Angular docs).
  2. Start using the new types (FormGroupTyped<T>, FormControlTyped<T>, etc.) whenever you need a strong type validation (see examples in that gist).

How does it meet the acceptance criteria?

  1. Properties and events are strongly typed.
  2. Quickly creating a Form Group from a class type or instance is still impossible. It needs to be defined manually and typed to the required type (e.g. new FormGroup { ... } as FormGroupTyped<MyClass>).
  3. Minimum maintenance costs – if feature versions of Angular introduce breaking changes in the Reactive Forms, corresponding changes need to be applied to typings.d.ts.

Pretty good overall, considering it has almost no learning curve.

We could wrap up here, but what about all the NPM packages out there promising strongly typed forms?

Solution 2. Third-party npm package

Among numerous NPM packages wrapping the standard FormBuilder, FormGroup and FormControl classes and enforcing type checking, @ng-stack/forms is the most interesting one and it’s actively adding new features.

The library introduces façades for all form-related classes and has an interesting feature to supply a validation model for controls, so errors become strongly typed too and later can be fetched like control.errors.someErrorCode.

Judging by the outlined acceptance criteria, it meets only 1 out of 3 points:

  1. Properties and events are strongly typed.
  2. Creating a Form Group is not that different from the standard way – this.fb.group<ICustomerModel>({ ... } )).
  3. Feature Angular versions may require a new varsion of the library and keeping up with Angular can be a serious burden. Many similar projects have been abandoned soon after launch (e.g. ngx-strongly-typed-forms, ngx-typed-forms).

Conclusion

While there is no a satisfactory solution adding TypedForms.d.ts can help out many devs.

The community has to keep pushing the Angular team to rewrite the implementation of the Reactive Forms to honour the types of the associated class and this tweet thread gives hope that desired changes may come in Angular v9.

Please share your thoughts in the comments below, on Twitter or join the Reddit discussion.