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
Uh oh!
There was an error while loading. Please reload this page.
Uh oh!
There was an error while loading. Please reload this page.
-
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.41 votes ·
Beta Was this translation helpful? Give feedback.
All reactions