ng-dynamic-component icon indicating copy to clipboard operation
ng-dynamic-component copied to clipboard

Input and Output typings based on the component

Open Jrubzjeknf opened this issue 3 years ago • 4 comments

This is more of a post for helping other developers with their input and output typing. As we all know, Typescript's typing helps developers by preventing mistakes. I don't think the library has typings that helps the developer, so I created them myself.

Result: image image image

These are the typings that matter.

import { EventEmitter } from '@angular/core';

// Based on https://medium.com/dailyjs/typescript-create-a-condition-based-subset-types-9d902cea5b8c
type FilterFlags<Base, Condition> = {
	[Key in keyof Base]: Base[Key] extends Condition ? Key : never;
};
type AllowedNames<Base, Condition> = FilterFlags<Base, Condition>[keyof Base];
export type PropertiesOfType<Base, Condition> = Pick<Base, AllowedNames<Base, Condition>>;
export type PropertiesNotOfType<Base, Condition> = Omit<Base, AllowedNames<Base, Condition>>;

type PropertiesNotEventEmitterOrFunction<T> = PropertiesNotOfType<T, EventEmitter<any> | ((...x: any) => any)>
type EventEmitterProperties<T> = PropertiesOfType<T, EventEmitter<any>>;

export type NonReadonlyPartial<T> = {
	-readonly [K in keyof T]?: T[K];
};

/** Best effort: contains all public properties except EventEmitters and functions. */
export type AngularInputTypes<T> = NonReadonlyPartial<PropertiesNotEventEmitterOrFunction<T>>;

/** All EventEmitter properties. */
export type AngularOutputTypes<T> = EventEmitterProperties<T>;

type EventEmitterToFunction<T> = {
	-readonly [K in keyof T]?: T[K] extends EventEmitter<infer R> ? (value: R) => void : never;
};

export type AngularOutputFunctionTypes<T> = EventEmitterToFunction<AngularOutputTypes<T>>;

You can use that with a config object like this:

export interface NdcConfig<T> {
	component: ComponentType<T>;

	inputs: PopupBuilderPopupInputs<T>;

	outputs: AngularOutputFunctionTypes<T>;
}

Which you can use in your Angular template as follows:

<ndc-dynamic
	*ngIf="config"
	[ndcDynamicComponent]="config.component"
	[ndcDynamicInputs]="config.inputs"
	[ndcDynamicOutputs]="config.outputs"
></ndc-dynamic>

Feel free to use. Let me know if you have improvements. Also feel free to incorporate (parts of) this in the library if you wish.

Jrubzjeknf avatar Nov 10 '20 10:11 Jrubzjeknf

Hi, thanks for a great typings.

There is just one thing about inputs - they are not 100% correct as its based on type information only and inputs may be functions (which you excluded here).

Outputs however is more accurate since it's type is distinct from everything else.

I still would like to consider adding these types to the library as a helper types.

But I would not want to type with them directive inputs/outputs because this may be a potential breaking changes for users.

So on that note would you like to submit a PR to the lib? =)

gund avatar Nov 10 '20 11:11 gund

There is just one thing about inputs - they are not 100% correct as its based on type information only and inputs may be functions (which you excluded here).

Correct. It is (afaik) currently impossible to detect inputs based on the Input directive. Instead all properties must be used. It just excludes EventEmitters, since they are outputs, and functions, since it is not possible to make a distinction between a function property or a class method. I removed excluded methods, because they are more likely to be class methods than actual inputs. And in my opinion, if you're making a input property of type function, you're better off using an output instead.

But I would not want to type with them directive inputs/outputs because this may be a potential breaking changes for users.

Agreed. A library's api must be clear and the current types provided are fine. I just wanted something stricter to help with me and my components. Other users may have other requirements, I mostly provided the code to help with the property extraction and transformation, since that is actually the most difficult apart about the typings.

So on that note would you like to submit a PR to the lib? =)

Do you reckon it is useful? The input part is rather opinionated and often 'wrong', since it also shows non-@Input() public properties. I figured I'd just provide this code for developers who want to use it, with the communicated ceavats.

Jrubzjeknf avatar Nov 10 '20 12:11 Jrubzjeknf

I think if the type produced for inputs is more inclusive then just inputs then it's totally fine since devs still can assign inputs correctly and just have autocomplete on top - so yeas I see this as a useful thing =)

Maybe we can even create 2 types for inputs:

  • default that excludes only event emitters
  • stricter that excludes event emitters + functions

In this way if devs will have inputs as functions they will have an option to use autocomplete with less strict type.

So in total we can add 3 new type helpers:

  • ComponentOutputs<T>
  • ComponentInputs<T>
  • StrictComponentInputs<T>

What do you think?

gund avatar Nov 10 '20 14:11 gund

I realize I never responded to this.

Yes, I think that's a good idea. :)

Jrubzjeknf avatar Feb 14 '22 08:02 Jrubzjeknf

Hey, is there still any interest in this? If not I will close the issue at some point in the future.

gund avatar Aug 28 '22 01:08 gund