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

Fix module imported twice #21770

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.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
47 changes: 44 additions & 3 deletions MIGRATION.md
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,7 @@
- [Addon-a11y: Removed deprecated withA11y decorator](#addon-a11y-removed-deprecated-witha11y-decorator)
- [7.0 Vite changes](#70-vite-changes)
- [Vite builder uses Vite config automatically](#vite-builder-uses-vite-config-automatically)
- [Vite cache moved to node_modules/.cache/.vite-storybook](#vite-cache-moved-to-node_modulescachevite-storybook)
- [Vite cache moved to node\_modules/.cache/.vite-storybook](#vite-cache-moved-to-node_modulescachevite-storybook)
- [7.0 Webpack changes](#70-webpack-changes)
- [Webpack4 support discontinued](#webpack4-support-discontinued)
- [Babel mode v7 exclusively](#babel-mode-v7-exclusively)
Expand All @@ -40,6 +40,7 @@
- [7.0 Framework-specific changes](#70-framework-specific-changes)
- [Angular: Drop support for Angular \< 14](#angular-drop-support-for-angular--14)
- [Angular: Drop support for calling Storybook directly](#angular-drop-support-for-calling-storybook-directly)
- [Angular: Application providers and ModuleWithProviders](#angular-application-providers-and-modulewithproviders)
- [Angular: Removed legacy renderer](#angular-removed-legacy-renderer)
- [Next.js: use the `@storybook/nextjs` framework](#nextjs-use-the-storybooknextjs-framework)
- [SvelteKit: needs the `@storybook/sveltekit` framework](#sveltekit-needs-the-storybooksveltekit-framework)
Expand Down Expand Up @@ -76,7 +77,7 @@
- [Dropped addon-docs manual babel configuration](#dropped-addon-docs-manual-babel-configuration)
- [Dropped addon-docs manual configuration](#dropped-addon-docs-manual-configuration)
- [Autoplay in docs](#autoplay-in-docs)
- [Removed STORYBOOK_REACT_CLASSES global](#removed-storybook_react_classes-global)
- [Removed STORYBOOK\_REACT\_CLASSES global](#removed-storybook_react_classes-global)
- [parameters.docs.source.excludeDecorators defaults to true](#parametersdocssourceexcludedecorators-defaults-to-true)
- [7.0 Deprecations and default changes](#70-deprecations-and-default-changes)
- [storyStoreV7 enabled by default](#storystorev7-enabled-by-default)
Expand Down Expand Up @@ -928,6 +929,47 @@ Starting in 7.0, we drop support for Angular < 14

In Storybook 6.4 we have deprecated calling Storybook directly (`npm run storybook`) for Angular. In Storybook 7.0, we've removed it entirely. Instead you have to set up the Storybook builder in your `angular.json` and execute `ng run <your-project>:storybook` to start Storybook. Please visit https://github.com/storybookjs/storybook/tree/next/code/frameworks/angular to set up Storybook for Angular correctly.

#### Angular: Application providers and ModuleWithProviders

In Storybook 7.0 we use the new bootstrapApplication API to bootstrap a standalone component to the DOM. The component is configured in a way to respect your configured imports, declarations and schemas, which you can define via the `moduleMetadata` decorator imported from `@storybook/angular`.

This means also, that there is no root ngModule anymore. Previously you were able to add ModuleWithProviders, likely the result of a 'Module.forRoot()'-style call, to your 'imports' array of the moduleMetadata definition. This is now discouraged. Instead, you should use the `applicationConfig` decorator to add your application-wide providers. These providers will be passed to the bootstrapApplication function.

For example, if you want to configure BrowserAnimationModule in your stories, please extract the necessary providers the following way and provide them via the `applicationConfig` decorator:

```js
import { BrowserAnimationsModule } from '@angular/platform-browser/animations';
import { importProvidersFrom } from '@angular/core';
import { applicationConfig } from '@storybook/angular';

export default {
title: 'Example',
decorators: [
applicationConfig({
providers: [importProvidersFrom(BrowserAnimationsModule)],
}
],
};
```

You can also use the `provide-style` decorator to provide an application-wide service:

```js
import { provideAnimations } from '@angular/platform-browser/animations';
import { moduleMetadata } from '@storybook/angular';

export default {
title: 'Example',
decorators: [
applicationConfig({
providers: [provideAnimations()],
}),
],
};
```

Please visit https://angular.io/guide/standalone-components#configuring-dependency-injection for more information.

#### Angular: Removed legacy renderer

The `parameters.angularLegacyRendering` option is removed. You cannot use the old legacy renderer anymore.
Expand Down Expand Up @@ -1430,7 +1472,6 @@ This was a legacy global variable from the early days of react docgen. If you we

By default we don't render decorators in the Source/Canvas blocks. If you want to render decorators, you can set the parameter to `false`.


### 7.0 Deprecations and default changes

#### storyStoreV7 enabled by default
Expand Down
87 changes: 87 additions & 0 deletions code/frameworks/angular/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,8 @@
- [Setup Compodoc](#setup-compodoc)
- [Automatic setup](#automatic-setup)
- [Manual setup](#manual-setup)
- [moduleMetadata decorator](#modulemetadata-decorator)
- [applicationConfig decorator](#applicationconfig-decorator)
- [FAQ](#faq)
- [How do I migrate to a Angular Storybook builder?](#how-do-i-migrate-to-a-angular-storybook-builder)
- [Do you have only one Angular project in your workspace?](#do-you-have-only-one-angular-project-in-your-workspace)
Expand Down Expand Up @@ -165,6 +167,91 @@ const preview: Preview = {
export default preview;
```

## moduleMetadata decorator

If your component has dependencies on other Angular directives and modules, these can be supplied using the moduleMetadata decorator either for all stories or for individual stories.

```js
import { StoryFn, Meta, moduleMetadata } from '@storybook/angular';
import { SomeComponent } from './some.component';

export default {
component: SomeComponent,
decorators: [
// Apply metadata to all stories
moduleMetadata({
// import necessary ngModules or standalone components
imports: [...],
// declare components that are used in the template
declarations: [...],
// List of providers that should be available to the root component and all its children.
providers: [...],
}),
],
} as Meta;

const Template = (): StoryFn => (args) => ({
props: args,
});

export const Base = Template();

export const WithCustomProvider = Template();
WithCustomProvider.decorators = [
// Apply metadata to a specific story
moduleMetadata({
imports: [...],
declarations: [...],
providers: [...]
}),
];
```

## applicationConfig decorator

If your component relies on application-wide providers, like the ones defined by BrowserAnimationsModule or any other modules which use the forRoot pattern to provide a ModuleWithProviders, you can use the applicationConfig decorator to provide them to the [bootstrapApplication function](https://angular.io/guide/standalone-components#configuring-dependency-injection), which we use to bootstrap the component in Storybook.

```js

import { StoryFn, Meta, applicationConfig } from '@storybook/angular';
import { BrowserAnimationsModule, provideAnimations } from '@angular/platform-browser/animations';
import { importProvidersFrom } from '@angular/core';
import { ChipsModule } from './angular-src/chips.module';

export default {
component: ChipsGroupComponent,
decorators: [
// Apply application config to all stories
applicationConfig({
// List of providers and environment providers that should be available to the root component and all its children.
providers: [
...
// Import application-wide providers from a module
importProvidersFrom(BrowserAnimationsModule)
// Or use provide-style functions if available instead, e.g.
provideAnimations()
],
}),
],
} as Meta;

const Template = (): StoryFn => (args) => ({
props: args,
});

export const Base = Template();

export const WithCustomApplicationProvider = Template();

WithCustomApplicationProvider.decorators = [
// Apply application config to a specific story
// The configuration will not be merged with the global configuration defined in the export default block
applicationConfig({
providers: [...]
}),
];
```

## FAQ

### How do I migrate to a Angular Storybook builder?
Expand Down
31 changes: 15 additions & 16 deletions code/frameworks/angular/src/client/angular-beta/AbstractRenderer.ts
Original file line number Diff line number Diff line change
Expand Up @@ -124,22 +124,21 @@ export abstract class AbstractRenderer {

const analyzedMetadata = new PropertyExtractor(storyFnAngular.moduleMetadata, component);

const providers = [
// Providers for BrowserAnimations & NoopAnimationsModule
analyzedMetadata.singletons,
importProvidersFrom(
...analyzedMetadata.imports.filter((imported) => {
const { isStandalone } = PropertyExtractor.analyzeDecorators(imported);
return !isStandalone;
})
),
analyzedMetadata.providers,
storyPropsProvider(newStoryProps$),
].filter(Boolean);

const application = getApplication({ storyFnAngular, component, targetSelector });

const applicationRef = await bootstrapApplication(application, { providers });
const application = getApplication({
storyFnAngular,
component,
targetSelector,
analyzedMetadata,
});

const applicationRef = await bootstrapApplication(application, {
...storyFnAngular.applicationConfig,
providers: [
storyPropsProvider(newStoryProps$),
...analyzedMetadata.applicationProviders,
...(storyFnAngular.applicationConfig?.providers ?? []),
],
});

applicationRefs.add(applicationRef);

Expand Down
Original file line number Diff line number Diff line change
@@ -1,11 +1,11 @@
import { NgModule, Type, Component, EventEmitter, Input, Output } from '@angular/core';

import { TestBed } from '@angular/core/testing';
import { BrowserModule } from '@angular/platform-browser';
import { BehaviorSubject } from 'rxjs';
import { ICollection } from '../types';
import { getApplication } from './StorybookModule';
import { storyPropsProvider } from './StorybookProvider';
import { PropertyExtractor } from './utils/PropertyExtractor';

describe('StorybookModule', () => {
describe('getStorybookModuleMetadata', () => {
Expand Down Expand Up @@ -55,10 +55,13 @@ describe('StorybookModule', () => {
localFunction: () => 'localFunction',
};

const analyzedMetadata = new PropertyExtractor({}, FooComponent);

const application = getApplication({
storyFnAngular: { props },
component: FooComponent,
targetSelector: 'my-selector',
analyzedMetadata,
});

const { fixture } = await configureTestingModule({
Expand Down Expand Up @@ -91,10 +94,13 @@ describe('StorybookModule', () => {
},
};

const analyzedMetadata = new PropertyExtractor({}, FooComponent);

const application = getApplication({
storyFnAngular: { props },
component: FooComponent,
targetSelector: 'my-selector',
analyzedMetadata,
});

const { fixture } = await configureTestingModule({
Expand All @@ -117,10 +123,13 @@ describe('StorybookModule', () => {
};
const storyProps$ = new BehaviorSubject<ICollection>(initialProps);

const analyzedMetadata = new PropertyExtractor({}, FooComponent);

const application = getApplication({
storyFnAngular: { props: initialProps },
component: FooComponent,
targetSelector: 'my-selector',
analyzedMetadata,
});
const { fixture } = await configureTestingModule({
imports: [application],
Expand Down Expand Up @@ -170,10 +179,13 @@ describe('StorybookModule', () => {
};
const storyProps$ = new BehaviorSubject<ICollection>(initialProps);

const analyzedMetadata = new PropertyExtractor({}, FooComponent);

const application = getApplication({
storyFnAngular: { props: initialProps },
component: FooComponent,
targetSelector: 'my-selector',
analyzedMetadata,
});
const { fixture } = await configureTestingModule({
imports: [application],
Expand Down Expand Up @@ -208,13 +220,16 @@ describe('StorybookModule', () => {
};
const storyProps$ = new BehaviorSubject<ICollection>(initialProps);

const analyzedMetadata = new PropertyExtractor({}, FooComponent);

const application = getApplication({
storyFnAngular: {
props: initialProps,
template: '<p [style.color]="color"><foo [input]="input"></foo></p>',
},
component: FooComponent,
targetSelector: 'my-selector',
analyzedMetadata,
});
const { fixture } = await configureTestingModule({
imports: [application],
Expand Down Expand Up @@ -243,10 +258,13 @@ describe('StorybookModule', () => {
};
const storyProps$ = new BehaviorSubject<ICollection>(initialProps);

const analyzedMetadata = new PropertyExtractor({}, FooComponent);

const application = getApplication({
storyFnAngular: { props: initialProps },
component: FooComponent,
targetSelector: 'my-selector',
analyzedMetadata,
});
const { fixture } = await configureTestingModule({
imports: [application],
Expand Down Expand Up @@ -276,13 +294,19 @@ describe('StorybookModule', () => {
it('should display the component', async () => {
const props = {};

const analyzedMetadata = new PropertyExtractor(
{ entryComponents: [WithoutSelectorComponent] },
WithoutSelectorComponent
);

const application = getApplication({
storyFnAngular: {
props,
moduleMetadata: { entryComponents: [WithoutSelectorComponent] },
},
component: WithoutSelectorComponent,
targetSelector: 'my-selector',
analyzedMetadata,
});

const { fixture } = await configureTestingModule({
Expand All @@ -302,10 +326,13 @@ describe('StorybookModule', () => {
})
class FooComponent {}

const analyzedMetadata = new PropertyExtractor({}, FooComponent);

const application = getApplication({
storyFnAngular: { template: '' },
component: FooComponent,
targetSelector: 'my-selector',
analyzedMetadata,
});

const { fixture } = await configureTestingModule({
Expand Down
16 changes: 10 additions & 6 deletions code/frameworks/angular/src/client/angular-beta/StorybookModule.ts
Original file line number Diff line number Diff line change
@@ -1,15 +1,18 @@
import { StoryFnAngularReturnType } from '../types';
import { createStorybookWrapperComponent } from './StorybookWrapperComponent';
import { computesTemplateFromComponent } from './ComputesTemplateFromComponent';
import { PropertyExtractor } from './utils/PropertyExtractor';

export const getApplication = ({
storyFnAngular,
component,
targetSelector,
analyzedMetadata,
}: {
storyFnAngular: StoryFnAngularReturnType;
component?: any;
targetSelector: string;
analyzedMetadata: PropertyExtractor;
}) => {
const { props, styles, moduleMetadata = {} } = storyFnAngular;
let { template } = storyFnAngular;
Expand All @@ -22,14 +25,15 @@ export const getApplication = ({
/**
* Create a component that wraps generated template and gives it props
*/
return createStorybookWrapperComponent(
targetSelector,
return createStorybookWrapperComponent({
moduleMetadata,
selector: targetSelector,
template,
component,
storyComponent: component,
styles,
moduleMetadata,
props
);
initialProps: props,
analyzedMetadata,
});
};

function hasNoTemplate(template: string | null | undefined): template is undefined {
Expand Down
Loading