Skip to content

Angular: Component creation with TestBed API#31311

Closed
christoph-rogalla wants to merge 70 commits into
storybookjs:nextfrom
christoph-rogalla:angular-dependencies
Closed

Angular: Component creation with TestBed API#31311
christoph-rogalla wants to merge 70 commits into
storybookjs:nextfrom
christoph-rogalla:angular-dependencies

Conversation

@christoph-rogalla
Copy link
Copy Markdown

@christoph-rogalla christoph-rogalla commented Apr 29, 2025

Targets #31088

What I did

The PR modifies the component creation for a story using Angular's TestBed API. The PR includes a TestBed component builder that follows the method chaining pattern. It carries all the component initialization data.

Checklist for Contributors

Testing

The changes in this PR are covered in the following automated tests:

  • stories
  • unit tests
  • integration tests
  • end-to-end tests

Manual testing

  1. Run a sandbox for template "angular-cli-default-ts"
  2. Open Storybook in your browser
  3. Try out diffrent Stories

Documentation

  • Add or update documentation reflecting your changes
  • If you are deprecating/removing a feature, make sure to update
    MIGRATION.MD

Checklist for Maintainers

  • When this PR is ready for testing, make sure to add ci:normal, ci:merged or ci:daily GH label to it to run a specific set of sandboxes. The particular set of sandboxes can be found in code/lib/cli-storybook/src/sandbox-templates.ts

  • Make sure this PR contains one of the labels below:

    Available labels
    • bug: Internal changes that fixes incorrect behavior.
    • maintenance: User-facing maintenance tasks.
    • dependencies: Upgrading (sometimes downgrading) dependencies.
    • build: Internal-facing build tooling & test updates. Will not show up in release changelog.
    • cleanup: Minor cleanup style change. Will not show up in release changelog.
    • documentation: Documentation only changes. Will not show up in release changelog.
    • feature request: Introducing a new feature.
    • BREAKING CHANGE: Changes that break compatibility in some way with current major version.
    • other: Changes that don't fit in the above categories.

🦋 Canary release

This pull request has been released as version 0.0.0-pr-31311-sha-927762ce. Try it out in a new sandbox by running npx storybook@0.0.0-pr-31311-sha-927762ce sandbox or in an existing project with npx storybook@0.0.0-pr-31311-sha-927762ce upgrade.

More information
Published version 0.0.0-pr-31311-sha-927762ce
Triggered by @valentinpalkovic
Repository christoph-rogalla/storybook
Branch angular-dependencies
Commit 927762ce
Datetime Wed Jul 23 19:52:21 UTC 2025 (1753300341)
Workflow run 16480444265

To request a new release of this pull request, mention the @storybookjs/core team.

core team members can create a new canary release here or locally with gh workflow run --repo storybookjs/storybook canary-release-pr.yml --field pr=31311

Greptile Summary

Introduces TestBed API integration for Angular component rendering in Storybook, with significant architectural changes to support component testing and lifecycle management.

  • Added new TestBedComponentBuilder in code/frameworks/angular/src/client/angular-beta/utils/TestBedComponentBuilder.ts implementing the builder pattern for TestBed configuration
  • Added support for standalone and non-standalone components through code/frameworks/angular/src/client/angular-beta/TestBedRenderer.ts
  • Upgraded Angular dependencies to v20.0.0 in code/frameworks/angular/package.json with expanded peer dependency range
  • Introduced new story examples in code/frameworks/angular/template/stories/ demonstrating TestBed integration
  • Added safe cleanup handling in code/frameworks/angular/src/client/config.ts to prevent test environment leaks

Copy link
Copy Markdown
Contributor

@greptile-apps greptile-apps Bot left a comment

Choose a reason for hiding this comment

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

7 file(s) reviewed, 14 comment(s)
Edit PR Review Bot Settings | Greptile


const applicationRefs = new Map<HTMLElement, ApplicationRef>();

const componentBuilders: TestBedComponentBuilder[] = [];
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

logic: Memory leak potential - componentBuilders array grows but never cleared

setStoryFn(storyFn: StoryFnAngularReturnType) {
this.styles = storyFn.styles;
this.schemas = storyFn.moduleMetadata?.schemas;
console.log(this.schemas);
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

style: Remove debug console.log statement before merging

Suggested change
console.log(this.schemas);

Comment on lines +119 to +121
if (this.props != null)
this.fixture.componentInstance = Object.assign(this.fixture.componentInstance, this.props);
this.fixture.detectChanges();
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

logic: Potential race condition if updateComponentProps is called before fixture is initialized. Add null check for this.fixture

Suggested change
if (this.props != null)
this.fixture.componentInstance = Object.assign(this.fixture.componentInstance, this.props);
this.fixture.detectChanges();
if (this.props != null && this.fixture) {
this.fixture.componentInstance = Object.assign(this.fixture.componentInstance, this.props);
this.fixture.detectChanges();
}


private component: Type<unknown> | undefined;

private fixture: ComponentFixture<unknown>;
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

logic: fixture is used before initialization - should be marked as optional with '?' or initialized in constructor

Comment on lines 4 to 7
@Component({
selector,
template,
standalone: true,
imports: [StorybookComponentModule],
providers,
styles,
schemas: moduleMetadata.schemas,
})
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

logic: Missing selector property in @component decorator could cause issues with component instantiation

) => {
return {
set: {
exports: [...declarations, ...imports],
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

logic: Spreading both declarations and imports into exports could cause naming conflicts if components have the same selectors

this.label = apiService.data;
}

label = 'NotSetYet';
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

logic: label property initialized after constructor runs - move initialization to constructor to avoid potential undefined state

Suggested change
label = 'NotSetYet';
label: string;

Comment on lines +18 to +20
constructor(private apiService: ApiService) {
this.label = apiService.data;
}
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

style: consider making apiService readonly since it's only used in constructor

Copy link
Copy Markdown
Contributor

@greptile-apps greptile-apps Bot left a comment

Choose a reason for hiding this comment

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

7 file(s) reviewed, 2 comment(s)
Edit PR Review Bot Settings | Greptile

Comment on lines +40 to +44
this.testBedInstance = new TestBed();
this.testBedInstance.initTestEnvironment(
BrowserDynamicTestingModule,
platformBrowserDynamicTesting()
);
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

logic: TestBed environment is initialized in constructor which could cause issues if multiple instances are created. Consider moving to a static initialization or adding checks to prevent multiple initializations.

Suggested change
this.testBedInstance = new TestBed();
this.testBedInstance.initTestEnvironment(
BrowserDynamicTestingModule,
platformBrowserDynamicTesting()
);
if (!TestBed.initialized) {
TestBed.initTestEnvironment(
BrowserDynamicTestingModule,
platformBrowserDynamicTesting()
);
}
this.testBedInstance = TestBed;

exports: [...declarations, ...imports],
declarations: declarations,
imports: imports,
providers: environmentProvider,
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

logic: environmentProvider could be undefined - add null check similar to other parameters

Suggested change
providers: environmentProvider,
providers: environmentProvider ?? [],

@valentinpalkovic valentinpalkovic self-assigned this Apr 29, 2025
@valentinpalkovic valentinpalkovic added feature request angular ci:normal Run our default set of CI jobs (choose this for most PRs). labels Apr 29, 2025
@shilman
Copy link
Copy Markdown
Member

shilman commented Apr 30, 2025

Amazing work, @christoph-rogalla!!! @valentinpalkovic should this be labeled with a breaking change label? Or is it fully backward compatible?

@shilman shilman changed the title Refactoring Component Creation with TestBed-Api Angular: Component creation with TestBed API Apr 30, 2025
Copy link
Copy Markdown
Contributor

@greptile-apps greptile-apps Bot left a comment

Choose a reason for hiding this comment

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

1 file(s) reviewed, 1 comment(s)
Edit PR Review Bot Settings | Greptile

Comment on lines +23 to +27
private imports: any[] = [];

private declarations: any[] = [];

private componentProviders: any[] = [];
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

style: Consider using more specific types instead of any[] for better type safety

@valentinpalkovic
Copy link
Copy Markdown
Contributor

valentinpalkovic commented Apr 30, 2025

@shilman, I don't think we can merge it in the next few days. The plan was to make the TestBed API work, get all of our Angular stories green, and then think about a way to integrate it in a non-breaking way in a second step, so that people have time in 9 to migrate, and in Storybook 10, we will remove the old renderer.

@github-actions github-actions Bot added the Stale label Aug 25, 2025
@github-actions github-actions Bot removed the Stale label Oct 6, 2025
@github-actions github-actions Bot added the Stale label Oct 18, 2025
@valentinpalkovic valentinpalkovic marked this pull request as draft December 1, 2025 13:41
@valentinpalkovic
Copy link
Copy Markdown
Contributor

@christoph-rogalla any chance to get this over the finish line?

@fgirardey
Copy link
Copy Markdown

@valentinpalkovic Is there any chance that a Storybook core maintainer can take over this PR to finish this?

It's been a while now that we can inject dependencies at the Component level in Angular and sadly we have ugly workarounds in my our codebase to inject mocked dependencies in page level components.

@valentinpalkovic
Copy link
Copy Markdown
Contributor

Some of our internal story tests are still failing, and we currently have no internal resources to bring this PR across the finish line. Is someone from @storybookjs/angular interested in fixing the remaining issues? I would also be happy if someone from the community took over the remaining work.

@valentinpalkovic
Copy link
Copy Markdown
Contributor

Closing for now due to its out-of-date state

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

angular ci:normal Run our default set of CI jobs (choose this for most PRs). feature request needs triage Stale

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants