From 7e70359c665b02bfb626e33a1f38863f73928b89 Mon Sep 17 00:00:00 2001 From: Michael Shilman Date: Wed, 8 Jun 2022 09:53:12 +0800 Subject: [PATCH] Merge pull request #18272 from leosvelperez/angular/standalone-components Angular: Support Angular 14 standalone components --- .../preview/angular-beta/StorybookModule.ts | 12 ++++-- .../utils/NgComponentAnalyzer.test.ts | 41 +++++++++++++++++++ .../angular-beta/utils/NgComponentAnalyzer.ts | 12 ++++++ 3 files changed, 62 insertions(+), 3 deletions(-) diff --git a/app/angular/src/client/preview/angular-beta/StorybookModule.ts b/app/angular/src/client/preview/angular-beta/StorybookModule.ts index 68c443375a83..275b490c18bc 100644 --- a/app/angular/src/client/preview/angular-beta/StorybookModule.ts +++ b/app/angular/src/client/preview/angular-beta/StorybookModule.ts @@ -7,7 +7,7 @@ import deprecate from 'util-deprecate'; import { ICollection, StoryFnAngularReturnType } from '../types'; import { storyPropsProvider } from './StorybookProvider'; import { isComponentAlreadyDeclaredInModules } from './utils/NgModulesAnalyzer'; -import { isDeclarable } from './utils/NgComponentAnalyzer'; +import { isDeclarable, isStandaloneComponent } from './utils/NgComponentAnalyzer'; import { createStorybookWrapperComponent } from './StorybookWrapperComponent'; import { computesTemplateFromComponent } from './ComputesTemplateFromComponent'; @@ -61,6 +61,7 @@ export const getStorybookModuleMetadata = ( props ); + const isStandalone = isStandaloneComponent(component); // Look recursively (deep) if the component is not already declared by an import module const requiresComponentDeclaration = isDeclarable(component) && @@ -68,7 +69,8 @@ export const getStorybookModuleMetadata = ( component, moduleMetadata.declarations, moduleMetadata.imports - ); + ) && + !isStandalone; return { declarations: [ @@ -76,7 +78,11 @@ export const getStorybookModuleMetadata = ( ComponentToInject, ...(moduleMetadata.declarations ?? []), ], - imports: [BrowserModule, ...(moduleMetadata.imports ?? [])], + imports: [ + BrowserModule, + ...(isStandalone ? [component] : []), + ...(moduleMetadata.imports ?? []), + ], providers: [storyPropsProvider(storyProps$), ...(moduleMetadata.providers ?? [])], entryComponents: [...(moduleMetadata.entryComponents ?? [])], schemas: [...(moduleMetadata.schemas ?? [])], diff --git a/app/angular/src/client/preview/angular-beta/utils/NgComponentAnalyzer.test.ts b/app/angular/src/client/preview/angular-beta/utils/NgComponentAnalyzer.test.ts index 03eba18a7236..faa7cdce09ff 100644 --- a/app/angular/src/client/preview/angular-beta/utils/NgComponentAnalyzer.test.ts +++ b/app/angular/src/client/preview/angular-beta/utils/NgComponentAnalyzer.test.ts @@ -19,6 +19,7 @@ import { isComponent, isDeclarable, getComponentDecoratorMetadata, + isStandaloneComponent, } from './NgComponentAnalyzer'; describe('getComponentInputsOutputs', () => { @@ -235,6 +236,46 @@ describe('isComponent', () => { }); }); +describe('isStandaloneComponent', () => { + it('should return true with a Component with "standalone: true"', () => { + // TODO: `standalone` is only available in Angular v14. Remove cast to `any` once + // Angular deps are updated to v14.x.x. + @Component({ standalone: true } as any) + class FooComponent {} + + expect(isStandaloneComponent(FooComponent)).toEqual(true); + }); + + it('should return false with a Component with "standalone: false"', () => { + // TODO: `standalone` is only available in Angular v14. Remove cast to `any` once + // Angular deps are updated to v14.x.x. + @Component({ standalone: false } as any) + class FooComponent {} + + expect(isStandaloneComponent(FooComponent)).toEqual(false); + }); + + it('should return false with a Component without the "standalone" property', () => { + @Component({}) + class FooComponent {} + + expect(isStandaloneComponent(FooComponent)).toEqual(false); + }); + + it('should return false with simple class', () => { + class FooPipe {} + + expect(isStandaloneComponent(FooPipe)).toEqual(false); + }); + + it('should return false with Directive', () => { + @Directive() + class FooDirective {} + + expect(isStandaloneComponent(FooDirective)).toEqual(false); + }); +}); + describe('getComponentDecoratorMetadata', () => { it('should return Component with a Component', () => { @Component({ selector: 'foo' }) diff --git a/app/angular/src/client/preview/angular-beta/utils/NgComponentAnalyzer.ts b/app/angular/src/client/preview/angular-beta/utils/NgComponentAnalyzer.ts index ae88972dfe26..36bd036631e3 100644 --- a/app/angular/src/client/preview/angular-beta/utils/NgComponentAnalyzer.ts +++ b/app/angular/src/client/preview/angular-beta/utils/NgComponentAnalyzer.ts @@ -108,6 +108,18 @@ export const isComponent = (component: any): component is Type => { return (decorators || []).some((d) => d instanceof Component); }; +export const isStandaloneComponent = (component: any): component is Type => { + if (!component) { + return false; + } + + const decorators = reflectionCapabilities.annotations(component); + + // TODO: `standalone` is only available in Angular v14. Remove cast to `any` once + // Angular deps are updated to v14.x.x. + return (decorators || []).some((d) => d instanceof Component && (d as any).standalone); +}; + /** * Returns all component decorator properties * is used to get all `@Input` and `@Output` Decorator