Skip to content

Angular: Detect model() signal outputs (type inference + compodoc autodocs + runtime binding)#34833

Merged
valentinpalkovic merged 20 commits into
nextfrom
valentin/angular-model-signal-outputs
May 21, 2026
Merged

Angular: Detect model() signal outputs (type inference + compodoc autodocs + runtime binding)#34833
valentinpalkovic merged 20 commits into
nextfrom
valentin/angular-model-signal-outputs

Conversation

@valentinpalkovic
Copy link
Copy Markdown
Contributor

@valentinpalkovic valentinpalkovic commented May 19, 2026

What

Adds native @storybook/angular detection of the output that Angular's model() signal generates. For color = model<string>(), Angular creates input color and output colorChange (input name + Change). Storybook previously detected none of it, in three independent layers. This fixes all three.

Closes #34831

Why

Reported via customer feedback (Omnissa) migrating to signal-based inputs/outputs. The component author had to hand-maintain a separate Args interface passed to Meta — and that workaround only silenced the type error; autodocs and runtime actions stayed broken.

How — 3 layers (stacked, independently-revertable commits)

  1. Layer A — type inference (public-types.ts): new TransformModelSignalType<T> composed innermost in TransformComponentType so a model() field yields both color: T and a synthesized ${K}Change: (e: T) => void key. Meta<C> now accepts colorChange in argTypes with no TS2353.
  2. Layer B — compodoc autodocs (compodoc.ts): compodoc emits a model() prop in both inputsClass and outputsClass (same bare name, no marker). Detection uses that both-arrays signal to synthesize a ${name}Change output argType deterministically under both angularFilterNonInputControls states.
  3. Layer C — runtime binding & actions (NgComponentAnalyzer.ts)

Testing

  • yarn nx check angular ✅, yarn nx compile angular ✅, yarn lint ✅ (0 new errors)
  • Targeted Angular vitest green, zero regression: NgComponentAnalyzer.test.ts, ComputesTemplateFromComponent.test.ts (109), docs/angular-properties.test.ts + compodoc.test.ts. A new non-factory NgComponentAnalyzer test block (the existing signal-I/O block stays commented behind a tracked TODO(angular-22) for the unrelated ComponentFactoryResolver removal) includes a throws-if-called spy proving the model() path never touches resolveComponentFactory.
  • The model-signal play stories follow the existing signal/ precedent and are validated in the Angular sandbox / test-runner in CI (the internal Storybook is React-based; Angular play stories are not runnable in the local unit harness — same as the existing signal/ stories).

Reviewed by automated architecture, code-quality, and security passes (all approved; findings addressed in the final commit).

Manual testing

  1. Generate the Angular sandbox: yarn task sandbox --template angular-cli/default-ts --start-from auto, then start it.
  2. Open the model-signal/ColorPicker stories.
  3. ControlsAndActions: edit the color Control and confirm the rendered value updates; click Set green and confirm colorChange fires in the Actions panel — no hand-written Args interface or manual argTypes.
  4. TwoWayRoundTrip: change the color Control and confirm it reaches the component; click Set green and confirm the emission writes back to the color arg (two-way [(color)]).
  5. Open the Docs (autodocs) tab and confirm color is listed as an input and colorChange as an output.

Known limitations (documented in code)

  • Aliased model(prop, { alias }): the type layer can only synthesize ${propName}Change (TypeScript cannot see the runtime alias); the runtime layer resolves the alias correctly via ɵcmp at AOT. model.required() is fully covered.
  • compodoc discriminator: a developer-authored same-name @Input() x + @Output() x pair would be misclassified as a model(). Inherent to detecting via an external/unpinned tool that emits no model() marker.

Caveat for reviewers

nx run-many -t check surfaces a pre-existing, unrelated nextjs-vite:check TS2451 (duplicate storybookNextJsPlugin declaration) that exists verbatim on next and is untouched by every commit here — flagged for separate triage so it doesn't mask this PR's signal.

🤖 Generated with Claude Code

Summary by CodeRabbit

  • New Features

    • Added support for Angular's model() API for two-way binding signals.
    • Enhanced component property detection to recognize signal-based inputs and outputs.
    • Automatically synthesized change outputs for model properties.
    • Updated type transformations so model-backed members map to arg values and synthesized change handlers.
  • Tests

    • Added comprehensive test coverage for signal-based component properties and type-level assertions.
    • Added Storybook stories demonstrating model signal two-way binding and round-trip synchronization.
    • Added compodoc/test fixtures and snapshots for model-based components.

Review Change Stack

valentinpalkovic and others added 5 commits May 19, 2026 00:08
Adds Layer A native detection of Angular model() signal outputs in the
@storybook/angular public type inference.

Refs #34831

- Adds AngularModelSignal / AngularHasModelSignal / ModelSignal aliases
  mirroring the existing InputSignal/OutputEmitterRef conditional style.
- Adds TransformModelSignalType<T>: maps each ModelSignal<E> field to E and
  synthesizes an intersection member `${prop}Change`: (e: E) => void (the
  xChange member is compiler-synthesized, never a real keyof T member).
- Pins TransformComponentType composition with TransformModelSignalType as the
  INNERMOST wrapper (do not reorder): the synthesized `${prop}Change` is
  (e:E)=>void so it passes the outer Input/Output/Event transforms unchanged,
  and since ModelSignal extends InputSignal the model value field is
  idempotently re-collapsed by the outer TransformInputSignalType (no
  double-transform divergence).
- Adds public-types.test-d.ts asserting the FINAL composed
  TransformComponentType<C> (composed, NOT TransformModelSignalType in
  isolation) for color/colorChange + model.required() + full no-regression
  coverage (input(), transform input(), output(), EventEmitter, @input,
  @output) resolving simultaneously in one type.

Documented limitation (for the AC-X3 changelog): aliased
model(prop, { alias: 'a' }) produces aChange at runtime, but Layer A can only
synthesize ${propName}Change because TypeScript cannot observe the runtime
alias. Runtime detection (Layer C) handles the alias via the resolved binding
name on ɵcmp. model.required() is fully covered.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Extends getComponentInputsOutputs with an additive dual detection path so a
model() field surfaces BOTH its `color` input and the compiler-synthesized
`colorChange` output, without altering @Input/@Output/input()/output()/
EventEmitter results from the decorator path.

Strategy (Probe C: esbuild/JIT strips signal AOT metadata in the unit-test
harness, so `ecmp` I/O maps are empty for signal members / `signals===false`):
- Primary: read the Angular component def via ɵgetComponentDef; ɵcmp keys the
  I/O maps by template name -> propName (verified empirically), so aliased
  model(x,{alias}) and model.required() resolve to their real binding names.
- Fallback: synthesize from the component instance brand (writable+subscribable
  signal => model() input + ${name}Change output) for non-AOT/JIT classes.

All 3 consumers verified (no edits needed): computesTemplateFromComponent and
computesTemplateSourceFromComponent emit [color]+(colorChange) via the existing
pure I/O builders; StorybookWrapperComponent filter-inversion confirmed -- the
model input now reaches the instance through the template Input binding
(initial render + live storyProps$ updates) instead of the dropped
getNonInputsOutputsProps direct-assignment path.

Adds a NEW factory-free test block covering @Input/@output, input(), output(),
EventEmitter, model(), model.required(), aliased model(), plus a throws-if-called
ComponentFactoryResolver guard proving zero factory invocation in model()
detection. L50-212 kept commented with a tracked TODO(angular-22).

Refs #34831

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
…er B)

Refs #34831

Angular `model()` two-way binding signals were not surfaced in compodoc
autodocs. compodoc (verified against the captured v1.2.1 output) emits a
`model()` member as an IDENTICAL entry — same bare name, no decorators/
jsdoctags, ModelSignal<T> wrapper erased — in BOTH `inputsClass` AND
`outputsClass`. Plain @Input/input() only land in inputsClass; plain
@Output/output()/EventEmitter only in outputsClass. The reliable,
version-tolerant discriminator is therefore a property whose name appears
in BOTH arrays of the same component (the both-arrays discriminator);
compodoc emits no model() marker, so compodoc-types.ts Property is unchanged.

- extractArgTypesFromData now detects model props via the both-arrays
  discriminator, suppresses compodoc's spurious bare-name outputsClass
  duplicate (model surfaces as an input control), and synthesizes a
  `${name}Change` output (action: '${name}Change') reusing the per-item
  output shape.
- Deterministic angularFilterNonInputControls re-surface branch: the
  synthesized `${name}Change` output + companion input are present with the
  flag OFF, and still re-surfaced with the flag ON despite iteration being
  restricted to ['inputsClass'].
- New __testfixtures__/doc-model fixture (mirrors doc-button) with the
  captured compodoc JSON; angular-properties.test.ts now asserts the
  synthesized colorChange/showTextChange rows for both filter states.
- doc-button EventEmitter fixture regression-guarded (untouched, still green).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Stacked commit 4 of 4 (cross-cutting) for native @storybook/angular
model() signal output detection.

Refs: #34831

- AC-X2a: ControlsAndActions story for a model() ColorPickerComponent
  (color = model<string>('#345F92')) asserting colorChange appears as an
  Action (fires on emit) and color is a Control/arg. Mirrors the existing
  Angular signal/ story precedent (template/stories_angular-cli-*/signal/,
  cli/page.stories.ts play pattern) exactly.
- AC-X2b: TwoWayRoundTrip story whose play runs the exact sequence:
  (1) initial render -> initial args.color reached the instance;
  (2) live storyProps$ arg change via useArgs().updateArgs -> new value
  reaches the instance (the StorybookWrapperComponent L125->L131 live
  path AC-C3c flags highest-risk); (3) in-component colorChange emission
  round-trips back to args.color (positive two-way [(color)]);
  (4) action received colorChange.
- AC-X3: CHANGELOG.prerelease.md entry under 10.5.0-alpha.0 describing
  native model() support (type inference + compodoc autodocs + runtime
  binding/actions) and that the hand-written Args workaround is no longer
  required. Explicitly documents the KNOWN LIMITATION: aliased
  model(prop,{alias}) - the type layer (Layer A) can only synthesize
  ${propName}Change, not the runtime alias; runtime (Layer C) resolves
  the alias via the Angular component def at AOT. model.required() fully
  covered.

AC-X1 full verification (real output):
- yarn nx run-many -t check: angular:check -> "No type errors" (Layers
  A/B/C clean). Single failing task nextjs-vite:check (TS2451
  storybookNextJsPlugin redeclaration) is PRE-EXISTING and unrelated -
  nextjs-vite is not touched by any of the 4 stacked commits; zero NEW
  errors from A/B/C/X.
- yarn nx run-many -t compile: exit 0 (42 projects).
- yarn lint: exit 0 (clean).
- yarn fmt:write: applied (4451 files; only the new story files +
  changelog modified, no unrelated changes).
- Targeted Angular vitest from repo root (NgComponentAnalyzer,
  ComputesTemplateFromComponent, docs/angular-properties, compodoc):
  14 files / 137 tests passed, no type errors, exit 0 - zero regression
  from Layers A/B/C.

Story-execution environment constraint (honest, FALLBACK - not a faked
green): the storybook-ui Vitest project (cd code && yarn storybook:vitest)
is the React-based internal Storybook; its include globs cover only
core/** and addons/*/** - frameworks/angular/template/** is not included,
so `yarn vitest run --project storybook-ui color-picker.stories` exits 1
with "No test files found". The @storybook/angular unit harness has no
AOT Vite plugin and does not glob *.stories.ts. No angular-cli sandbox
exists locally. The model() play stories therefore run only in a
generated Angular sandbox in CI - the exact established validation path
for the pre-existing template/stories_angular-cli-*/signal/ precedent.
The stories are authored correctly as real CI/sandbox-validated
deliverables. Full detail recorded in
.omc/plans/probe-results-angular-model-signal-outputs.md
(STEP 4 story-execution environment section).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
…s, comments, limitations)

comment-only repoint to committed compodoc-input.json fixture; document same-name @Input/@output discriminator false-positive (compodoc.ts + changelog); synthesized ${name}Change output no longer inherits misleading input defaultValue/type summary (Docs-table only, detection logic unchanged); reference #34831.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai Bot commented May 19, 2026

Note

Reviews paused

It looks like this branch is under active development. To avoid overwhelming you with review comments due to an influx of new commits, CodeRabbit has automatically paused this review. You can configure this behavior by changing the reviews.auto_review.auto_pause_after_reviewed_commits setting.

Use the following commands to manage reviews:

  • @coderabbitai resume to resume automatic reviews.
  • @coderabbitai review to trigger a single review.

Use the checkboxes below for quick actions:

  • ▶️ Resume reviews
  • 🔍 Trigger review
📝 Walkthrough

Walkthrough

This PR adds end-to-end Angular model() signal support to Storybook. Changes include type-level transformations that synthesize ${K}Change handlers, runtime analysis of compiled component definitions to detect signal-based I/O, Compodoc extraction logic to recognize and expose model properties, comprehensive test fixtures and snapshots, feature-flag-aware integration tests, and example Storybook stories demonstrating two-way binding behavior.

Changes

Angular model() Signal Support

Layer / File(s) Summary
Type-level model() signal transformation
code/frameworks/angular/src/client/public-types.ts, code/frameworks/angular/src/client/public-types.test-d.ts
Adds TransformModelSignalType<T> to map ModelSignal<E> to E and synthesize ${K}Change handlers typed as (e: E) => void; integrates as innermost step in TransformComponentType pipeline with feature detection (AngularHasModelSignal); declaration tests validate model() type inference and ${name}Change synthesis across model(), input(), output(), and decorator-based channels.
Runtime model() detection in NgComponentAnalyzer
code/frameworks/angular/src/client/angular-beta/utils/NgComponentAnalyzer.ts, code/frameworks/angular/src/client/angular-beta/utils/NgComponentAnalyzer.test.ts
Refactors getComponentInputsOutputs to build decorator-derived I/O and augment via new addSignalInputsOutputs helper that reads compiled ɵcmp definition; normalizes signal input/output forms and deduplicates entries by propName or templateName; test suite adds signal-based I/O coverage including model() producing both input and synthesized ${name}Change output entries with alias mapping.
Compodoc model() extraction and synthesis
code/frameworks/angular/src/client/compodoc.ts
Detects model properties by comparing inputsClass and outputsClass bare names; suppresses duplicate bare-name outputsClass entries for model props in main iteration; synthesizes ${name}Change OUTPUT argTypes with function signatures (e: ${type}) => void and OUTPUT-table metadata.
Test fixtures and snapshot data
code/frameworks/angular/src/client/docs/__testfixtures__/doc-model/input.ts, code/frameworks/angular/src/client/docs/__testfixtures__/doc-model/tsconfig.json, code/frameworks/angular/src/client/docs/__testfixtures__/doc-model/compodoc-*.snapshot, code/frameworks/angular/src/client/docs/__testfixtures__/doc-model/argtypes*.snapshot
Adds ColorPickerComponent fixture with model() for two-way color binding; fixture tsconfig; snapshot files capturing Compodoc JSON output and extracted argTypes both with and without filter feature enabled.
Integration tests for documentation extraction
code/frameworks/angular/src/client/docs/angular-properties.test.ts
Feature-flag-aware Vitest suite that loads fixtures, snapshots Compodoc captures (OS-suffixed), and validates extracted argTypes with filter OFF (default expectations) and ON (ensuring model inputs and synthesized change outputs both appear); resets flag between runs.
Storybook example stories (default template)
code/frameworks/angular/template/stories_angular-cli-default-ts/model-signal/color-picker.component.ts, code/frameworks/angular/template/stories_angular-cli-default-ts/model-signal/color-picker.css, code/frameworks/angular/template/stories_angular-cli-default-ts/model-signal/color-picker.stories.ts
Adds ColorPickerComponent with color model, stylesheet, and stories; ControlsAndActions verifies initial render and action spy on colorChange; TwoWayRoundTrip uses useArgs/updateArgs to simulate two-way binding and validates round-trip updates.
Storybook example stories (prerelease template)
code/frameworks/angular/template/stories_angular-cli-prerelease/model-signal/color-picker.component.ts, code/frameworks/angular/template/stories_angular-cli-prerelease/model-signal/color-picker.css, code/frameworks/angular/template/stories_angular-cli-prerelease/model-signal/color-picker.stories.ts
Adds ColorPickerComponent with color model, stylesheet, and stories; ControlsAndActions asserts rendered color and action spy on colorChange; TwoWayRoundTrip uses custom render with useArgs() to simulate two-way binding and validates arg round-trips.

🎯 3 (Moderate) | ⏱️ ~25 minutes


Comment @coderabbitai help to get the list of available commands and usage tips.

Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

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

🧹 Nitpick comments (2)
code/frameworks/angular/template/stories_angular-cli-default-ts/model-signal/color-picker.stories.ts (1)

6-6: ⚡ Quick win

Add explicit .ts file extension to component import.

The coding guidelines recommend explicit file extensions for relative TypeScript module imports. As per coding guidelines: "For TypeScript source in the repo, prefer explicit file extensions for relative code imports and exports such as ./foo.ts or ./bar.tsx when the target is another TS/JS module".

♻️ Suggested fix
-import ColorPickerComponent from './color-picker.component';
+import ColorPickerComponent from './color-picker.component.ts';
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In
`@code/frameworks/angular/template/stories_angular-cli-default-ts/model-signal/color-picker.stories.ts`
at line 6, Update the relative import to include the explicit TypeScript
extension: change the import that references ColorPickerComponent from
'./color-picker.component' to reference './color-picker.component.ts' so the
module import for ColorPickerComponent follows the repo guideline of using
explicit .ts extensions for TypeScript source files.
code/frameworks/angular/template/stories_angular-cli-prerelease/model-signal/color-picker.stories.ts (1)

6-6: ⚡ Quick win

Add explicit .ts file extension to component import.

The coding guidelines recommend explicit file extensions for relative TypeScript module imports. As per coding guidelines: "For TypeScript source in the repo, prefer explicit file extensions for relative code imports and exports such as ./foo.ts or ./bar.tsx when the target is another TS/JS module".

♻️ Suggested fix
-import ColorPickerComponent from './color-picker.component';
+import ColorPickerComponent from './color-picker.component.ts';
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In
`@code/frameworks/angular/template/stories_angular-cli-prerelease/model-signal/color-picker.stories.ts`
at line 6, The import of ColorPickerComponent should use an explicit TypeScript
extension; update the import statement that references color-picker.component to
import from './color-picker.component.ts' (i.e., change the module specifier
used when importing ColorPickerComponent) so it follows the repo guideline for
explicit .ts extensions on relative TypeScript imports.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Nitpick comments:
In
`@code/frameworks/angular/template/stories_angular-cli-default-ts/model-signal/color-picker.stories.ts`:
- Line 6: Update the relative import to include the explicit TypeScript
extension: change the import that references ColorPickerComponent from
'./color-picker.component' to reference './color-picker.component.ts' so the
module import for ColorPickerComponent follows the repo guideline of using
explicit .ts extensions for TypeScript source files.

In
`@code/frameworks/angular/template/stories_angular-cli-prerelease/model-signal/color-picker.stories.ts`:
- Line 6: The import of ColorPickerComponent should use an explicit TypeScript
extension; update the import statement that references color-picker.component to
import from './color-picker.component.ts' (i.e., change the module specifier
used when importing ColorPickerComponent) so it follows the repo guideline for
explicit .ts extensions on relative TypeScript imports.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 9fc35a57-8e44-4c09-864f-1e8ab9050c39

📥 Commits

Reviewing files that changed from the base of the PR and between 3ae861f and 332fe51.

📒 Files selected for processing (21)
  • CHANGELOG.prerelease.md
  • code/frameworks/angular/src/client/angular-beta/utils/NgComponentAnalyzer.test.ts
  • code/frameworks/angular/src/client/angular-beta/utils/NgComponentAnalyzer.ts
  • code/frameworks/angular/src/client/compodoc.ts
  • code/frameworks/angular/src/client/docs/__testfixtures__/doc-model/argtypes-filtered.snapshot
  • code/frameworks/angular/src/client/docs/__testfixtures__/doc-model/argtypes.snapshot
  • code/frameworks/angular/src/client/docs/__testfixtures__/doc-model/compodoc-input.json
  • code/frameworks/angular/src/client/docs/__testfixtures__/doc-model/compodoc-posix.snapshot
  • code/frameworks/angular/src/client/docs/__testfixtures__/doc-model/compodoc-undefined.snapshot
  • code/frameworks/angular/src/client/docs/__testfixtures__/doc-model/compodoc-windows.snapshot
  • code/frameworks/angular/src/client/docs/__testfixtures__/doc-model/input.ts
  • code/frameworks/angular/src/client/docs/__testfixtures__/doc-model/tsconfig.json
  • code/frameworks/angular/src/client/docs/angular-properties.test.ts
  • code/frameworks/angular/src/client/public-types.test-d.ts
  • code/frameworks/angular/src/client/public-types.ts
  • code/frameworks/angular/template/stories_angular-cli-default-ts/model-signal/color-picker.component.ts
  • code/frameworks/angular/template/stories_angular-cli-default-ts/model-signal/color-picker.css
  • code/frameworks/angular/template/stories_angular-cli-default-ts/model-signal/color-picker.stories.ts
  • code/frameworks/angular/template/stories_angular-cli-prerelease/model-signal/color-picker.component.ts
  • code/frameworks/angular/template/stories_angular-cli-prerelease/model-signal/color-picker.css
  • code/frameworks/angular/template/stories_angular-cli-prerelease/model-signal/color-picker.stories.ts

@storybook-app-bot
Copy link
Copy Markdown

storybook-app-bot Bot commented May 19, 2026

Package Benchmarks

Commit: 9bf6059, ran on 21 May 2026 at 10:05:58 UTC

No significant changes detected, all good. 👏

…ic ɵcmp only

Address review feedback (Karpathy + Pragmatic):

- NgComponentAnalyzer: drop the new-instance fallback (runInInjectionContext +
  new component()) and its orphaned imports. Signal I/O is now read purely
  statically from the compiled component definition (ɵcmp), so the analysis
  path never executes user constructor code. Verified the fallback was
  test-harness-only (ComputesTemplateFromComponent/compodoc/angular-properties
  unaffected).
- Tests: assert the production ɵcmp reader via a synthetic ɵcmp mirroring the
  AOT shape (now also covers aliased model() resolution). Drop the
  over-engineered throws-if-called resolveComponentFactory guard; restore
  pre-existing imports.
- Strip internal investigation jargon (Probe/AC/R6/Lxxx) from comments in
  NgComponentAnalyzer, compodoc, public-types, and the model-signal stories.
- CHANGELOG: collapse to the repo one-line convention.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

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

🧹 Nitpick comments (1)
code/frameworks/angular/src/client/angular-beta/utils/NgComponentAnalyzer.test.ts (1)

219-310: ⚡ Quick win

Add one mixed decorator+signal case here.

The new suite validates each source independently, but the production change is the merge between decorator-derived metadata and ɵcmp. A single component that combines @Input/@Output with input()/output()/model() would lock down the dedupe path and catch duplicate/alias ordering regressions in addSignalInputsOutputs().

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In
`@code/frameworks/angular/src/client/angular-beta/utils/NgComponentAnalyzer.test.ts`
around lines 219 - 310, Add a new test in the same suite that creates a single
component combining decorator-based members and a synthetic ɵcmp (use the
withCmp helper) to exercise getComponentInputsOutputs and the merge/dedupe path
in addSignalInputsOutputs; create a class FooComponent with `@Input/`@Output
(e.g., input, inputWithBindingPropertyName, output,
outputWithBindingPropertyName) and then attach a ɵcmp that includes signal-style
inputs/outputs (arrays and strings) including names that would collide or alias
(e.g., same templateName or synthesized model change names) and assert the final
inputs and outputs include all unique props with the decorator values taking
precedence where intended and no duplicates (use sortByPropName/containsEqual
assertions similar to the existing cases).
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Nitpick comments:
In
`@code/frameworks/angular/src/client/angular-beta/utils/NgComponentAnalyzer.test.ts`:
- Around line 219-310: Add a new test in the same suite that creates a single
component combining decorator-based members and a synthetic ɵcmp (use the
withCmp helper) to exercise getComponentInputsOutputs and the merge/dedupe path
in addSignalInputsOutputs; create a class FooComponent with `@Input/`@Output
(e.g., input, inputWithBindingPropertyName, output,
outputWithBindingPropertyName) and then attach a ɵcmp that includes signal-style
inputs/outputs (arrays and strings) including names that would collide or alias
(e.g., same templateName or synthesized model change names) and assert the final
inputs and outputs include all unique props with the decorator values taking
precedence where intended and no duplicates (use sortByPropName/containsEqual
assertions similar to the existing cases).

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 12febf9b-6d96-4d64-a6e7-036e7a83e34f

📥 Commits

Reviewing files that changed from the base of the PR and between 332fe51 and a74018a.

📒 Files selected for processing (7)
  • CHANGELOG.prerelease.md
  • code/frameworks/angular/src/client/angular-beta/utils/NgComponentAnalyzer.test.ts
  • code/frameworks/angular/src/client/angular-beta/utils/NgComponentAnalyzer.ts
  • code/frameworks/angular/src/client/compodoc.ts
  • code/frameworks/angular/src/client/public-types.ts
  • code/frameworks/angular/template/stories_angular-cli-default-ts/model-signal/color-picker.stories.ts
  • code/frameworks/angular/template/stories_angular-cli-prerelease/model-signal/color-picker.stories.ts
✅ Files skipped from review due to trivial changes (1)
  • CHANGELOG.prerelease.md
🚧 Files skipped from review as they are similar to previous changes (4)
  • code/frameworks/angular/src/client/compodoc.ts
  • code/frameworks/angular/template/stories_angular-cli-prerelease/model-signal/color-picker.stories.ts
  • code/frameworks/angular/src/client/public-types.ts
  • code/frameworks/angular/template/stories_angular-cli-default-ts/model-signal/color-picker.stories.ts

valentinpalkovic and others added 2 commits May 19, 2026 13:17
Cut comments that restated the code or repeated the rationale across call site
and JSDoc; kept the load-bearing ones (ɵcmp shape, type-composition order,
compodoc both-arrays heuristic + its limitation).

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
- Remove the CHANGELOG.prerelease.md entry (reverts to next).
- public-types.test-d.ts: drop "Layer A"/"AC-X3" transcript prose,
  tighten the transform-input comment.
- public-types.ts: condense the TransformComponentType JSDoc to the
  load-bearing do-NOT-reorder constraint.
- compodoc.ts: broaden the model() false-positive note (inherited /
  accessor splits) and document the aliased-model() autodocs gap.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 1

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@code/frameworks/angular/src/client/public-types.ts`:
- Around line 111-117: TransformModelSignalType is incorrectly unwrapping model
values in the first mapping (it turns ModelSignal<E> into E), causing valid
model-like types (e.g., EventEmitter<number>, OutputEmitterRef<string>) to be
mistaken for handlers; change the first mapped part to preserve the original
member type T[K] (do not unwrap ModelSignal) and keep only the second mapped
part that synthesizes the `${K}Change` handler when T[K] extends
ModelSignal<infer E>, typing that handler as (e: E) => void so that later types
like TransformEventType / TransformOutputSignalType can correctly unwrap model
fields.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 6fc3613e-bcfb-45d2-b247-26bd94ba1f46

📥 Commits

Reviewing files that changed from the base of the PR and between 2f86d33 and 19175ef.

📒 Files selected for processing (3)
  • code/frameworks/angular/src/client/compodoc.ts
  • code/frameworks/angular/src/client/public-types.test-d.ts
  • code/frameworks/angular/src/client/public-types.ts
🚧 Files skipped from review as they are similar to previous changes (1)
  • code/frameworks/angular/src/client/compodoc.ts

Comment thread code/frameworks/angular/src/client/public-types.ts
`useArgs()` is a preview hook and throws when called in a `play`
function ("hooks can only be called inside decorators and story
functions"), which crashed the TwoWayRoundTrip story and failed
Chromatic with 1 component error.

Update args from `play` via the channel (UPDATE_STORY_ARGS +
STORY_RENDERED), matching the existing core/svelte precedent, and
tag the story `!vitest` since live arg updates disrupt the runner.
The valid render-side useArgs() write-back is unchanged.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Comment thread code/frameworks/angular/src/client/public-types.ts Outdated
Comment thread code/frameworks/angular/src/client/compodoc.ts
Comment thread code/frameworks/angular/src/client/angular-beta/utils/NgComponentAnalyzer.test.ts Outdated
Comment thread code/frameworks/angular/src/client/docs/angular-properties.test.ts Outdated
Co-authored-by: Valentin Palkovic <dev@valentinpalkovic.dev>
Copy link
Copy Markdown
Member

@Sidnioulz Sidnioulz left a comment

Choose a reason for hiding this comment

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

I don't know Angular enough to validate the actual interfacing between Angular and Storybook, but the code looks sound to me. Manual testing passed.

Comment thread code/frameworks/angular/src/client/docs/angular-properties.test.ts Outdated
@valentinpalkovic
Copy link
Copy Markdown
Contributor Author

@claude, fix CI and apply the requested changes.

@valentinpalkovic
Copy link
Copy Markdown
Contributor Author

@copilot fix CI and apply the requested changes.

Copy link
Copy Markdown
Contributor

Copilot AI commented May 21, 2026

@copilot fix CI and apply the requested changes.

Done in a6d1715. I applied the requested review changes, checked CI status via GitHub Actions checks, and re-ran local targeted Angular validation. The remaining nextjs-vite TS2451 failure is the same pre-existing issue already noted in the PR description.

Warning

Firewall rules blocked me from connecting to one or more addresses (expand for details)

I tried to connect to the following addresses, but was blocked by firewall rules:

  • app.circleci.com
    • Triggering command: /home/REDACTED/work/_temp/ghcca-node/node/bin/node /home/REDACTED/work/_temp/ghcca-node/node/bin/node --enable-source-maps /home/REDACTED/work/_temp/copilot-developer-action-main/dist/index.js (dns block)

If you need me to access, download, or install something from one of these locations, you can either:

@valentinpalkovic valentinpalkovic added the patch:yes Bugfix & documentation PR that need to be picked to main branch label May 21, 2026
@valentinpalkovic valentinpalkovic merged commit de29a47 into next May 21, 2026
123 of 133 checks passed
@valentinpalkovic valentinpalkovic deleted the valentin/angular-model-signal-outputs branch May 21, 2026 11:30
@github-project-automation github-project-automation Bot moved this from In Progress to Done in Core Team Projects May 21, 2026
@github-actions github-actions Bot added the patch:done Patch/release PRs already cherry-picked to main/release branch label May 22, 2026
RuBrock pushed a commit to RuBrock/storybook that referenced this pull request May 23, 2026
…r-model-signal-outputs

Angular: Detect model() signal outputs (type inference + compodoc autodocs + runtime binding)
(cherry picked from commit de29a47)
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

bug ci:normal patch:done Patch/release PRs already cherry-picked to main/release branch patch:yes Bugfix & documentation PR that need to be picked to main branch

Projects

Status: Done

Development

Successfully merging this pull request may close these issues.

[Bug]: Angular model() signals — generated ${name}Change output not detected (types + compodoc autodocs + runtime binding)

3 participants