Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Angular: Support Angular 14 standalone components #18272

Merged
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
12 changes: 9 additions & 3 deletions app/angular/src/client/preview/angular-beta/StorybookModule.ts
Expand Up @@ -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';

Expand Down Expand Up @@ -61,22 +61,28 @@ 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) &&
!isComponentAlreadyDeclaredInModules(
component,
moduleMetadata.declarations,
moduleMetadata.imports
);
) &&
!isStandalone;

return {
declarations: [
...(requiresComponentDeclaration ? [component] : []),
ComponentToInject,
...(moduleMetadata.declarations ?? []),
],
imports: [BrowserModule, ...(moduleMetadata.imports ?? [])],
imports: [
BrowserModule,
...(isStandalone ? [component] : []),
Copy link
Contributor

@ThibaudAV ThibaudAV Jun 7, 2022

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I initially added a unit test there, but it fails because the current Angular version in this repo doesn't support importing components in an NgModule, so it throws an error. I could still add a unit test but I won't be able to check the fixture (can't call configureTestingModule(ngModule) because it errors). All I could check is that the right imports and declarations are present in the NgModule resultant of calling getStorybookModuleMetadata.

...(moduleMetadata.imports ?? []),
],
providers: [storyPropsProvider(storyProps$), ...(moduleMetadata.providers ?? [])],
entryComponents: [...(moduleMetadata.entryComponents ?? [])],
schemas: [...(moduleMetadata.schemas ?? [])],
Expand Down
Expand Up @@ -19,6 +19,7 @@ import {
isComponent,
isDeclarable,
getComponentDecoratorMetadata,
isStandaloneComponent,
} from './NgComponentAnalyzer';

describe('getComponentInputsOutputs', () => {
Expand Down Expand Up @@ -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' })
Expand Down
Expand Up @@ -108,6 +108,18 @@ export const isComponent = (component: any): component is Type<unknown> => {
return (decorators || []).some((d) => d instanceof Component);
};

export const isStandaloneComponent = (component: any): component is Type<unknown> => {
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
Expand Down