Replies: 2 comments
-
I've created a working typing for inputs and outputs which supports one way and 2 way inputs + outputs here. Type usage looks like this: interface MyComponent {
input1?: string;
input2?: number;
input1Change: 'fake-output-type';
output1: EventEmitter<boolean>;
input2Change: EventEmitter<number>;
}
/** Produces type:
{
[x: `[attr.${string}]`]: string | null | undefined;
[x: `[class.${string}]`]: string | boolean | null | undefined;
[x: `[style.${string}]`]: unknown;
[x: string]: unknown;
input1?: string | undefined;
input2?: number | undefined;
input1Change?: "fake-output-type" | undefined;
"[input1]"?: string | undefined;
"[input2]"?: number | undefined;
"[input1Change]"?: "fake-output-type" | undefined;
"([input2])"?: string | undefined;
"(output1)"?: ((event: boolean) => void) | undefined;
"(input2Change)"?: ((event: number) => void) | undefined;
}
*/
type MyComponentIo = IO<MyComponent>;
const io: MyComponentIo = {
input1: 'val1',
'([input2])': 'propName',
'(output1)': (event) => console.log(event),
'[attr.id]': 'some-id',
'[class.active]': true,
'[style.width.px]': 20,
}; See the code for typing implementation// Stub for EventEmitter from @angular/core
interface EventEmitter<T> {
__isEventEmitter: true;
}
type InferEventEmitter<T> = T extends EventEmitter<infer E> ? E : unknown;
type SkipPropsByType<T, TSkip> = {
[K in keyof T]: T[K] extends TSkip ? never : K;
}[keyof T];
type PickPropsWithOutputs<
O extends string | number | symbol,
I extends string | number | symbol,
> = O extends `${infer K}Change` ? (K extends I ? K : never) : never;
type Inputs<K extends keyof T, T> = Pick<T, K>;
type InputProps<K extends keyof T, T> = {
[P in K as `[${P & string}]`]: T[P];
};
type Inputs2Way<K> = {
[P in K as `([${P & string}])`]: string;
};
type InputsAttrs = {
[P in []as `[attr.${string}]`]?: string | null;
};
type InputsClasses = {
[P in []as `[class.${string}]`]?: string | boolean | null;
};
type InputsStyles = {
[P in []as `[style.${string}]`]?: unknown;
};
type Outputs<K extends keyof T, T> = {
[P in K as `(${P & string})`]: (event: InferEventEmitter<T[P]>) => void;
};
type IO<
T,
I extends keyof T = SkipPropsByType<T, EventEmitter<any>>,
O extends keyof T = Exclude<keyof T, I>,
I2W extends keyof T = PickPropsWithOutputs<O, I>,
> = Partial<
Inputs<I, T>
& InputProps<I, T>
& Inputs2Way<I2W>
& Outputs<O, T>
& InputsAttrs
& InputsClasses
& InputsStyles
& Record<string, unknown>
>; It still allows for any input/output as a fallback as it's possible to set random html attributes/events on the components. |
Beta Was this translation helpful? Give feedback.
0 replies
-
Created a working basic template syntax parser to IO object here. |
Beta Was this translation helpful? Give feedback.
0 replies
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
-
Since Typescript started support of template strings it is now possible to define a type that will modify properties and methods using a string templates.
This can be leveraged to merge inputs and outputs into a single input object and use native Angular template syntax to differentiate between inputs and outputs, for example:
This will enable the library to also add support for native 2-way binding just like Angular does:
Another benefit is that we can support other nice native Angular syntax like
attr.
,class.
,style.
, etc.:For type safety we could map all props as
(propName)
+([propName])
for 2-way binding and EventEmitters as(outputName)
, however even current state does not have types for inputs and outputs so this is not really required and may be done optionally.Another syntax could be as simple as a string with the standard Angular template syntax which then will be interpolated and the object will be created which will bring an even cleaner API (however typing a string is probably not going to work well):
The drawback of this combined IO is that there is going to be a slight overhead of processing inputs/outputs initially but not during rendering/updates of the dynamic component itself so it should be minimal.
Also this could be introduced as a separate directive for mixed IO so the current way with 2 separate directives is not affected.
The open question is how to handle props that have no specified syntax (eg.
{input: prop}
), should we treat it as a property binding by default as Angular does or just throw an error? I'm more inclined to treat is as a property binding.37 votes ·
Beta Was this translation helpful? Give feedback.
All reactions