Skip to content

Commit

Permalink
feat: remove deprecated API based on inheritance
Browse files Browse the repository at this point in the history
BREAKING CHANGE: The old API is not available anymore
  • Loading branch information
maxime1992 committed Dec 7, 2022
1 parent 69cc63c commit 4ab50c5
Show file tree
Hide file tree
Showing 105 changed files with 615 additions and 4,151 deletions.
2 changes: 1 addition & 1 deletion .github/workflows/main.yml
Original file line number Diff line number Diff line change
Expand Up @@ -48,7 +48,7 @@ jobs:
github_token: ${{ secrets.GITHUB_TOKEN }}
publish_dir: ./dist/ngx-sub-form-demo
- name: Release
if: contains('refs/heads/master refs/heads/next refs/heads/feat-rewrite', github.ref)
if: contains('refs/heads/master refs/heads/next', github.ref)
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
NPM_TOKEN: ${{ secrets.NPM_TOKEN }}
Expand Down
7 changes: 1 addition & 6 deletions .releaserc
Original file line number Diff line number Diff line change
@@ -1,11 +1,6 @@
{
"pkgRoot": "dist/ngx-sub-form",
"branches": [
"master",
{
"name": "feat-rewrite",
"channel": "feat-rewrite",
"prerelease": true
}
"master"
]
}
77 changes: 10 additions & 67 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,8 +6,6 @@ Utility library to improve the robustness of your Angular forms.

Whether you have simple and tiny forms or huge and complex ones, `ngx-sub-form` will help you build a solid base for them.

- 🗜️ Tiny bundle
_(currently ~30kb as we support both the old api and the new one but soon to be ~15kb!)_
- ✅ Simple API: No angular module to setup, no `ControlValueAccessor` by hand, no inheritance, no boilerplate. Only one function to create all your forms!
- 🤖 Adds type safety to your forms
- ✂️ Lets you break down huge forms into smaller ones for simplicity and reusability
Expand Down Expand Up @@ -43,69 +41,14 @@ npm i ngx-sub-form

**Note about the versions:**

| `@angular` version | `ngx-sub-form` version |
| ---------------------- | ---------------------- |
| <= `7` | <= `2.7.1` |
| `8.x` | `4.x` |
| `9.x`, `10.x` | `5.1.2` |
| `11.x`, `12.x`, `13.x` | `6.0.0` |

The major bump from version `5.1.2` to `6.0.0` doesn't bring any changes to the public API of `ngx-sub-form`.
It's only a major bump for Angular 11 support and you should be able to upgrade without having to update any of your forms.

That said, the version `6.0.0` also brings some exciting news!
We sneaked into that release a [complete rewrite of ngx-sub-form to get rid of inheritance](https://github.com/cloudnc/ngx-sub-form/issues/171) 🎉. The best part being: **It's been done in a non breaking way to guarantee a smooth upgrade, which can be done incrementally, one form at a time, from the old API to the new one**.

The old API has been marked as deprecated and will be removed in a few months as part of a major version bump, to give you time to upgrade.

# Migration guide to the new API

If your project is not using `ngx-sub-form` yet, feel free to skip this migration guide.
On the other hand, **if your project is using `ngx-sub-form` with the inheritance API please read the following**.

High level explanation:

- On the public API, the required changes are mostly moving things around as none of the core concepts have changed
- Depending on how much forms you have, this may be a long and boring task as we don't have any schematics to make those changes automatically for you
- On the bright side, it should be a fairly easy task in terms of complexity
- You should be able to make the upgrade **incrementally** as well _(form after form if you want to instead of a big bang rewrite!)_. This is because behind the scenes the root and sub forms communicate through the `ControlValueAccessor` interface and as this one is from Angular and didn't change, it should be fine updating one form at a time

The simplest thing to understand the new syntax is probably to have a look on the [basic API usage](#basic-api-usage) example which covers most of the cases. But let's describe a step by step approach how to update your forms:

- `createForm` is the new function to create both your root and sub forms. It's very similar in terms of configuration to all the attributes and methods that you needed to implement after extending from `NgxRootFormComponent` or `NgxSubFormComponent`
- The first parameter that you should be providing in the configuration object of `createForm` is `formType` which can be either `FormType.ROOT` or `FormType.SUB`
- Then, you can provide the following ones for a sub form:

- `formControls`
- `emitNullOnDestroy` _(optional)_
- `formGroupOptions` _(optional)_
- `toFormGroup` _(optional: If you have only 1 interface, required if you passed a second type to define a remap)_
- `fromFormGroup` _(optional: If you have only 1 interface, required if you passed a second type to define a remap)_

- And for a root form you **additionally** provide the following bindings:

- `input$`
- `output$`
- `disabled$`
- `manualSave$` _(optional)_
- `handleEmissionRate` _(optional)_

Most of the attributes and methods have the same name as they had before so it shouldn't be too much of a trouble to move from a class approach with attributes and methods to a configuration object.

On the template side, assuming that you've saved the return of the `createForm` in a `form` variable:

- `formGroupControls` will now be `form.formGroup.controls`
- `formGroupValues` will now be `form.formGroup.value`

We're exposing the original `formGroup` object but it has been augmented on the type level by making it a `TypedFormGroup<FormInterface>` which provides type safety on a bunch of attributes and methods (`value`, `valueChanges`, `controls`, `setValue`, `patchValue`, `getRawValue`). See the `TypedFormGroup` interface in `projects/ngx-sub-form/src/lib/shared/ngx-sub-form-utils.ts` if you want to know more. As a result of this, we now don't need to provide `formGroupControls` nor `formGroupValues` for type safety any more.

Previously, `transformToFormGroup` _(which is now as you guessed it `toFormGroup`)_ was taking as the first parameter `obj: ControlInterface | null` and as a second one `defaultValues: Partial<FormInterface> | null`. This was pretty annoying as you needed to define a `getDefaultValues` method to provide your default values. Now you simply define your default values within the `formControls` function on each of the form controls as you'd expect. Behind the scenes, when the component is created for the first time we make a deep copy of those default values and apply them automatically if the root form or the sub form is being updated upstream with `null` or `undefined`.

If you were previously using inheritance to set some defaults globally, for example on your root forms for the `handleEmissionRate` method, you cannot do that anymore and you'll need to define those on a per component basis! So if you were extending your own class, itself inheriting from a root form or a sub form, don't forget about that. We're considering passing a token through DI to be able to set some of those settings globally. But it's not done yet and give us feedback if you think it should.

For root forms, the helper `DataInput` has been removed. It is now by default slightly more verbose to get the input data as you have to declare a `Subject` and push values into it yourself (by using either a setter on your input or the `ngOnChanges` hook). `DataInput` was originally created to reduce this boilerplate but as there are plenty of libraries available to transform an input into an observable, we let the choice to either do it manually or install a library on your side to transform the input into an observable for you.

You can also have a look into our demo app located here: `src/app`. You'll find `main` and `main-rewrite` which are exactly the same applications but `main` is using the deprecated API (the one with inheritance) while `main-rewrite` is using the new one. As those 2 applications showcase all the features of ngx-sub-form you can easily find what you're looking for and compare both if we forgot to cover anything. Just as an FYI, we've kept both apps for now which are tested by the same E2E test suite to make sure that nothing got broken on the old API during the rewrite. When we decide to remove the old API we'll of course remove the demo implementation which is using the old API.
| `@angular` version | `ngx-sub-form` version |
| -------------------- | ---------------------------------------------------- |
| v <= `7` | v <= `2.7.1` |
| `8.x` | `4.x` |
| `9.x` <= v <= `12.x` | `5.1.2` |
| `13.x` | `5.2.0` (non breaking but new API available as well) |
| `14.x` | `6.0.0` (Angular 14 upgrade only) |
| `14.x` | `7.0.0` (deprecated API is now removed) |

# API

Expand Down Expand Up @@ -367,7 +310,7 @@ Here are the 2 interfaces:

Example of a remap could be getting a date object that you want to convert to an ISO string date before passing that value to a date picker and before broadcasting that value back to the parent, convert it back to a date. Or vice versa.

A really interesting use case is to deal with polymorphic values. If we take the example of our live demo: https://cloudnc.github.io/ngx-sub-form we've got `src/app/main-rewrite/listing/listing-form/listing-form.component.ts`. This form can receive either a `vehicle` or a `droid`. While polymorphism works great on typescript side, when it comes to templates... It's an other story! The best way is to have 2 sub components, which will handle 1 and 1 thing: Either a `vehicle` **or** a `droid`. And in the template use an `ngIf` or an `ngSwitch` to dynamically create the expected sub form.
A really interesting use case is to deal with polymorphic values. If we take the example of our live demo: https://cloudnc.github.io/ngx-sub-form we've got `src/app/main/listing/listing-form/listing-form.component.ts`. This form can receive either a `vehicle` or a `droid`. While polymorphism works great on typescript side, when it comes to templates... It's an other story! The best way is to have 2 sub components, which will handle 1 and 1 thing: Either a `vehicle` **or** a `droid`. And in the template use an `ngIf` or an `ngSwitch` to dynamically create the expected sub form.
That said, to be able to `switch` on a value, we need to know that value: A discriminator. It'll let us know what's the type of our current object really easily, without having to create a [type guard](https://www.typescriptlang.org/docs/handbook/advanced-types.html#type-guards-and-differentiating-types) for example. And a remap is a perfect candidate for this. If you want a full example please have a look to the `listing-form.component.ts` _(path shown above)_.

## Dealing with arrays
Expand All @@ -386,7 +329,7 @@ createFormArrayControl(key, value) => FormControl;

Where key is a key of your main form and value, its associated value.

To see a complete example please refer to `src/app/main-rewrite/listing/listing-form/vehicle-listing/crew-members/crew-members.component.ts` and its `html` part.
To see a complete example please refer to `src/app/main/listing/listing-form/vehicle-listing/crew-members/crew-members.component.ts` and its `html` part.

# Contribution

Expand Down
Loading

0 comments on commit 4ab50c5

Please sign in to comment.