Which @angular/* package(s) are relevant/related to the feature request?
core
Description
As the Angular signal ecosystem has evolved, the framework introduced several specialised signal variants beyond the base signal: component inputs (input, input.required), view queries (viewChild(ren), viewChild.required), and content queries (contentChild(ren), contentChild.required), as well as derived signals (computed, linkedSignal).
Despite sharing a unified public interface (Signal<T>), each of these types carries distinct internal semantics that are critical for library authors to account for:
- Lifecycle readiness: special signals such as
input.required, viewChild.required, and contentChild.required cannot always be safely read synchronously before the first change detection cycle. Today, a library utility that accepts a Signal<T> has no way to detect this condition.
- Change detection: query signals (
viewChild, contentChild) are managed by Angular's change detection. Their value transitions are driven by the framework's render phases, not by user code. This distinction determines whether a library should use afterRenderEffect (delegating checks to Angular) or afterEveryRender (polling manually), a decision that currently requires introspecting private node properties such as _dirtyCounter.
In my library when implementing a utility of the form (pseudocode):
function onDisconnect<T extends Element>(target: Signal<T>, callback: (element: T) => void);
I need to inspect a signal to understand how I can more optimally track the disconnect logic. As mentioned above, at the moment I have to rely on accessing the node’s internal property via the [SIGNAL] symbol.
Proposed solution
A possible solution could be the following:
Introduce a reflectSignalType() function, following the precedent of reflectComponentType(), which returns a structured metadata object describing the runtime characteristics of a given signal:
interface SignalMetadata {
kind:
| 'signal' // signal()
| 'computed' // computed()
| 'linkedSignal' // linkedSignal()
| 'input' // input() / input.required()
| 'query' // viewChild() / viewChild.required()
required: boolean;
writable: boolean;
/* ... */
}
function reflectSignalType(signal: Signal<unknown>): SignalMetadata;
P.S.: In the internal implementation, there is already ReactiveNodeKind enum that describes all types of reactive nodes (including, for example, a pure consumer like effect). However, there is no separate kind for input/query signals. This might require further consideration.
Alternatives considered
Partial workarounds exist for introspection needs. It is possible to detect whether a signal is a query signal or an input signal by inspecting internal properties of the signal node accessed through the private [SIGNAL] symbol (an undocumented, unstable implementation detail with no guarantees across versions).
Library authors currently have no choice but to either ignore these distinctions entirely, or ask users to pass additional metadata manually alongside the signal - a significant ergonomic regression that puts the burden of framework-level knowledge onto the consumer.
Which @angular/* package(s) are relevant/related to the feature request?
core
Description
As the Angular signal ecosystem has evolved, the framework introduced several specialised signal variants beyond the base
signal: component inputs (input,input.required), view queries (viewChild(ren),viewChild.required), and content queries (contentChild(ren),contentChild.required), as well as derived signals (computed,linkedSignal).Despite sharing a unified public interface (
Signal<T>), each of these types carries distinct internal semantics that are critical for library authors to account for:input.required,viewChild.required, andcontentChild.requiredcannot always be safely read synchronously before the first change detection cycle. Today, a library utility that accepts aSignal<T>has no way to detect this condition.viewChild,contentChild) are managed by Angular's change detection. Their value transitions are driven by the framework's render phases, not by user code. This distinction determines whether a library should useafterRenderEffect(delegating checks to Angular) orafterEveryRender(polling manually), a decision that currently requires introspecting private node properties such as_dirtyCounter.In my library when implementing a utility of the form (pseudocode):
I need to inspect a signal to understand how I can more optimally track the disconnect logic. As mentioned above, at the moment I have to rely on accessing the node’s internal property via the
[SIGNAL]symbol.Proposed solution
A possible solution could be the following:
Introduce a
reflectSignalType()function, following the precedent of reflectComponentType(), which returns a structured metadata object describing the runtime characteristics of a given signal:P.S.: In the internal implementation, there is already
ReactiveNodeKindenum that describes all types of reactive nodes (including, for example, a pure consumer likeeffect). However, there is no separate kind for input/query signals. This might require further consideration.Alternatives considered
Partial workarounds exist for introspection needs. It is possible to detect whether a signal is a query signal or an input signal by inspecting internal properties of the signal node accessed through the private
[SIGNAL]symbol (an undocumented, unstable implementation detail with no guarantees across versions).Library authors currently have no choice but to either ignore these distinctions entirely, or ask users to pass additional metadata manually alongside the signal - a significant ergonomic regression that puts the burden of framework-level knowledge onto the consumer.