Skip to content

Stricter types #147

Open
Open
@maxime1992

Description

@maxime1992

Context

This is taking over #118 as this issue much bigger than just for adding new values into FormArrays.

Having a look into #146 and the stricter settings, I do realize that here:

  public listing$: Observable<NullableObject<OneListing>> = this.route.paramMap.pipe(
    map(params => params.get('listingId')),
    switchMap(listingId => {
      if (listingId === 'new' || !listingId) {
        return of(null);
      }
      return this.listingService.getOneListing(listingId);
    }),
    map(listing => (listing ? listing : this.emptyListing())),
  );

We're defining the listing$ as Observable<NullableObject<OneListing>>.

and here:

<app-listing-form
  [disabled]="readonlyFormControl.value"
  [listing]="listing$ | async"
  <----------------------------------------------
  (listingUpdated)="upsertListing($event)"
></app-listing-form>

We're passing that into the root form. But the root form takes as a parameter a OneListing, not a NullableObject<OneListing>:

https://github.com/cloudnc/ngx-sub-form/blob/v5.0.1/src/app/main/listing/listing-form/listing-form.component.ts#L39

@Component({
  selector: 'app-listing-form',
  templateUrl: './listing-form.component.html',
  styleUrls: ['./listing-form.component.scss'],
})
export class ListingFormComponent extends NgxRootFormComponent<OneListing, OneListingForm>

(which now throw a TS error with strict mode ON 🙌)

Issue

In a world where we'd just make edits, we could skip the NullableObject because we'd only receive objects of the exact type. But in reality we also want to have the possibility to create new objects (therefore they'd have all or some properties set as null when they're passed as input).

A good example of that is the one above with listing$: Observable<NullableObject<OneListing>>. We generate a new ID and pass all the other props as null.

Other example, if the form is being reset (without the default values). They'll all be set to null.

Question

Should we always provide an API that would make the input required + nullable props + nil and offer a new hook to run a strict check to make sure all the non nillables values are defined?

Example of a new API

We could have a new type:

export type DataInput<ControlInterface> =
  | NullableObject<Required<ControlInterface>>
  | null
  | undefined;

And for a component instead of having:

  @DataInput()
  @Input('listing')
  public dataInput: Required<OneListing> | null | undefined;

It'd be

  @DataInput()
  @Input('listing')
  public dataInput: DataInput<OneListing>;

Besides the friendlier syntax, DataInput uses NullableObject which is what I want to focus on here. This means that we'd be able to pass null properties on the object (but they should still all be defined), and as in the Output we still want a OneListing we could have a hook that'd check for the null values and throw an error if needed. This hook would be useful to fill up the gap between what we want and the checks ran on the FormGroup (in case we forget to add a Validators.required for example).

Demo:

export class ListingFormComponent extends NgxRootFormComponent<
  OneListing,
  OneListingForm
> {
  @DataInput()
  @Input('listing')
  public dataInput: DataInput<OneListing>;

  @Output('listingUpdated')
  public dataOutput: EventEmitter<OneListing> = new EventEmitter();

  // classic methods here...

  protected checkFormResourceBeforeSending(
    resource: NullableObject<OneListing>
  ): resource is OneListing {
    // do the check
  }
}

if checkFormResourceBeforeSending would return false then we should internally throw an error (as it should never happen).

Random thoughts

I wonder if:

  • We always want that to be true or sometimes be able to skip the creation and enforce to pass only values of the type itself (without nullable props)
  • checkFormResourceBeforeSending should be mandatory (could make things a bit more verbose...) or maybe just optional and would require to implement an interface. This may help at first and at some point we could potentially make it required? Also not sure how it'd work for people who are not using strict mode in TS

Metadata

Metadata

Assignees

No one assigned

    Labels

    effort-2: hoursWill only take a few hours to fix/createscope: libAnything related to the library itselfstate: needs designThis feature or fix should be discussed before writing any codetype: RFC/discussion/questionThis issue is about RFC, discussion or a question

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions