From 7a0a1c7bd5291bff138046091f98e3bd2ec8c033 Mon Sep 17 00:00:00 2001 From: Valentin Palkovic Date: Mon, 18 Mar 2024 08:36:23 +0100 Subject: [PATCH] Angular: Add support for Angular's output signals Support the new signal-based output function in Angular 17.3 and upwards in Storybook's helper types as StoryObj or StoryFn. I also ensured that TypeScript doesn't complain in Angular versions that don't support this feature. Please be aware, that controls might not reflect the proper types when Signals are used. For component and property analysis, we are relying on Compodoc. Compodoc doesn't support the new output signal yet. --- code/frameworks/angular/scripts/postbuild.js | 4 +++- .../angular/src/client/public-types.ts | 17 +++++++++++++---- .../signal/button.component.ts | 5 ++--- .../signal/button.component.ts | 5 ++--- 4 files changed, 20 insertions(+), 11 deletions(-) diff --git a/code/frameworks/angular/scripts/postbuild.js b/code/frameworks/angular/scripts/postbuild.js index fe9845007595..68af6389ac37 100644 --- a/code/frameworks/angular/scripts/postbuild.js +++ b/code/frameworks/angular/scripts/postbuild.js @@ -10,5 +10,7 @@ const path = require('path'); const filePath = path.join(__dirname, '../dist/client/public-types.d.ts'); const fileContent = fs.readFileSync(filePath, 'utf8'); -const newContent = fileContent.replaceAll(/(type AngularInputSignal)/g, '// @ts-ignore\n$1'); +const newContent = fileContent + .replaceAll(/(type AngularInputSignal)/g, '// @ts-ignore\n$1') + .replaceAll(/(type AngularOutputEmitterRef)/g, '// @ts-ignore\n$1'); fs.writeFileSync(filePath, newContent, 'utf8'); diff --git a/code/frameworks/angular/src/client/public-types.ts b/code/frameworks/angular/src/client/public-types.ts index b0eabc0312be..9469cfda489e 100644 --- a/code/frameworks/angular/src/client/public-types.ts +++ b/code/frameworks/angular/src/client/public-types.ts @@ -56,21 +56,30 @@ export type Preview = ProjectAnnotations; /** * Utility type that transforms InputSignal and EventEmitter types */ -type TransformComponentType = TransformInputSignalType> +type TransformComponentType = TransformInputSignalType>> // @ts-ignore Angular < 17.2 doesn't export InputSignal type AngularInputSignal = AngularCore.InputSignal // @ts-ignore Angular < 17.2 doesn't export InputSignalWithTransform type AngularInputSignalWithTransform = AngularCore.InputSignalWithTransform +// @ts-ignore Angular < 17.3 doesn't export AngularOutputEmitterRef +type AngularOutputEmitterRef = AngularCore.OutputEmitterRef -type AngularHasSignal = typeof AngularCore extends { input: infer U } ? true : false; -type InputSignal = AngularHasSignal extends true ? AngularInputSignal : never; -type InputSignalWithTransform = AngularHasSignal extends true ? AngularInputSignalWithTransform : never; +type AngularHasInputSignal = typeof AngularCore extends { input: infer U } ? true : false; +type AngularHasOutputSignal = typeof AngularCore extends { output: infer U } ? true : false; + +type InputSignal = AngularHasInputSignal extends true ? AngularInputSignal : never; +type InputSignalWithTransform = AngularHasInputSignal extends true ? AngularInputSignalWithTransform : never; +type OutputEmitterRef = AngularHasOutputSignal extends true ? AngularOutputEmitterRef : never; type TransformInputSignalType = { [K in keyof T]: T[K] extends InputSignal ? E : T[K] extends InputSignalWithTransform ? U : T[K]; }; +type TransformOutputSignalType = { + [K in keyof T]: T[K] extends OutputEmitterRef ? (e: E) => void : T[K]; +}; + type TransformEventType = { [K in keyof T]: T[K] extends AngularCore.EventEmitter ? (e: E) => void : T[K]; }; diff --git a/code/frameworks/angular/template/stories_angular-cli-default-ts/signal/button.component.ts b/code/frameworks/angular/template/stories_angular-cli-default-ts/signal/button.component.ts index ad4ae84890c0..4077bc044c9b 100644 --- a/code/frameworks/angular/template/stories_angular-cli-default-ts/signal/button.component.ts +++ b/code/frameworks/angular/template/stories_angular-cli-default-ts/signal/button.component.ts @@ -1,4 +1,4 @@ -import { Component, Input, Output, EventEmitter, input } from '@angular/core'; +import { Component, Input, input, output } from '@angular/core'; @Component({ // Needs to be a different name to the CLI template button @@ -40,8 +40,7 @@ export default class SignalButtonComponent { /** * Optional click handler */ - @Output() - onClick = new EventEmitter(); + onClick = output(); public get classes(): string[] { const mode = this.primary() ? 'storybook-button--primary' : 'storybook-button--secondary'; diff --git a/code/frameworks/angular/template/stories_angular-cli-prerelease/signal/button.component.ts b/code/frameworks/angular/template/stories_angular-cli-prerelease/signal/button.component.ts index ad4ae84890c0..4077bc044c9b 100644 --- a/code/frameworks/angular/template/stories_angular-cli-prerelease/signal/button.component.ts +++ b/code/frameworks/angular/template/stories_angular-cli-prerelease/signal/button.component.ts @@ -1,4 +1,4 @@ -import { Component, Input, Output, EventEmitter, input } from '@angular/core'; +import { Component, Input, input, output } from '@angular/core'; @Component({ // Needs to be a different name to the CLI template button @@ -40,8 +40,7 @@ export default class SignalButtonComponent { /** * Optional click handler */ - @Output() - onClick = new EventEmitter(); + onClick = output(); public get classes(): string[] { const mode = this.primary() ? 'storybook-button--primary' : 'storybook-button--secondary';