{{ message }}
fix(docs-infra): render signal inputs and outputs using initializer s… - #70566
Open
MeAkib wants to merge 1 commit into
Open
fix(docs-infra): render signal inputs and outputs using initializer s…#70566MeAkib wants to merge 1 commit into
MeAkib wants to merge 1 commit into
Conversation
JeanMeche
reviewed
Sep 5, 2026
JeanMeche
left a comment
Member
There was a problem hiding this comment.
This looks great, but there is one missing piece: model() bindings!
Currently, ModelSignal is completely omitted from the new signal prefix checks. Since model() is inherently both an input and an output, the docs extractor applies both MemberTags.Input and MemberTags.Output tags to it. Because it falls through the new checks, the docs generator will incorrectly render it with both legacy decorators like this:
readonly @Input() @Output('myModelChange') myModel: ModelSignal<string>;To fix this, we need to explicitly detect ModelSignal types and strip both the @Input and @Output tags when generating the code line.
You could add a check for ModelSignal and then handle it similarly inside getPropertyCodeLine:
/** Type-string prefix that identifies a signal-based model (`model()` / `model.required()`). */
const SIGNAL_MODEL_TYPE_PREFIXES = ['ModelSignal<'];
export function isSignalModel(member: PropertyEntry): boolean {
return (
(member.memberTags.includes(MemberTags.Input) || member.memberTags.includes(MemberTags.Output)) &&
SIGNAL_MODEL_TYPE_PREFIXES.some((prefix) => member.type?.startsWith(prefix))
);
}
// ... inside getPropertyCodeLine(member: PropertyEntry): string
export function getPropertyCodeLine(member: PropertyEntry): string {
if (isSignalModel(member)) {
// A model is both an input and an output. We need to strip both tags so they aren't rendered as decorators.
const memberWithoutTags = {
...member,
memberTags: member.memberTags.filter(t => t !== MemberTags.Input && t !== MemberTags.Output)
};
return getSignalBindingCodeLine(
memberWithoutTags,
MemberTags.Input, // getSignalBindingCodeLine expects a tag to filter out, but we've already done it
member.isRequiredInput ? 'model.required' : 'model',
member.inputAlias,
);
}
if (isSignalInput(member)) {
// ...
MeAkib
force-pushed
the
render-signal-inputs-using-initializer-syntax
branch
from
September 5, 2026 02:09
679c6a1 to
50ae3cc
Compare
…alizer syntax The API reference rendered signal-based bindings with a hybrid line mixing decorator syntax and the signal type, e.g. `readonly @input('formField') field: InputSignal<Field<T>>;` for a member declared as `readonly field = input.required<Field<T>>({alias: 'formField'});`. Signal bindings are never declared with `@Input()` / `@Output()`, so the rendered code did not match how the source is authored. Signal inputs, outputs and models are now rendered in the initializer form the source uses: readonly field = input.required<Field<T>>({alias: 'formField'}); readonly nameChange = output<string>({alias: 'name'}); readonly value = model<string>(); `PropertyEntry` carries no `isSignal` flag, so the discriminator is the resolved property type: `InputSignal` / `InputSignalWithTransform` for inputs, `OutputEmitterRef` for outputs and `ModelSignal` for models. A model is tagged as both an input and an output, so both tags are stripped and only its input alias is passed to `model()` — the output alias is always derived from it. `outputFromObservable()` is deliberately excluded: it is typed as the broader `OutputRef` and is not declared with an `output()` initializer. Decorator-based inputs and outputs continue to render with `@Input(...)` / `@Output(...)`.
MeAkib
force-pushed
the
render-signal-inputs-using-initializer-syntax
branch
from
September 5, 2026 02:10
50ae3cc to
206248b
Compare
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.

…yntax
The API reference rendered signal-based bindings with a hybrid line mixing decorator syntax and the signal type, e.g.
readonly @Input('formField') field: InputSignal<Field<T>>;for a member declared asreadonly field = input.required<Field<T>>({alias: 'formField'});. Signal bindings are never declared with@Input()/@Output(), so the rendered code did not match how the source is authored.Signal inputs and outputs are now rendered in the initializer form the source uses:
readonly field = input.required<Field>({alias: 'formField'});
readonly nameChange = output({alias: 'name'});
PropertyEntrycarries noisSignalflag, so the discriminator is the resolved property type:InputSignal/InputSignalWithTransformfor inputs andOutputEmitterReffor outputs.outputFromObservable()is deliberately excluded — it is typed as the broaderOutputRefand is not declared with anoutput()initializer. Decorator-based inputs and outputs continue to render with@Input(...)/@Output(...).What is the current behavior?
https://angular.dev/api/forms/signals/FormRoot
https://angular.dev/api/forms/signals/FormField
What is the new behavior?