66 * found in the LICENSE file at https://angular.io/license
77 */
88
9+ import { ComputedNode , SIGNAL , SignalNode } from '@angular/core/primitives/signals' ;
910import { ChangeDetectionStrategy } from '../../change_detection/constants' ;
1011import { Injector } from '../../di/injector' ;
1112import { ViewEncapsulation } from '../../metadata/view' ;
12- import { assertLView } from '../assert' ;
13+ import { throwError } from '../../util/assert' ;
14+ import { assertLView , assertTNode } from '../assert' ;
1315import {
1416 discoverLocalRefs ,
1517 getComponentAtNodeIndex ,
@@ -18,13 +20,28 @@ import {
1820 readPatchedLView ,
1921} from '../context_discovery' ;
2022import { getComponentDef , getDirectiveDef } from '../definition' ;
21- import { NodeInjector } from '../di' ;
23+ import { NodeInjector , getNodeInjectorLView , getNodeInjectorTNode } from '../di' ;
2224import { DirectiveDef } from '../interfaces/definition' ;
2325import { TElementNode , TNode , TNodeProviderIndexes } from '../interfaces/node' ;
24- import { CLEANUP , CONTEXT , FLAGS , LView , LViewFlags , TVIEW , TViewType } from '../interfaces/view' ;
26+ import {
27+ CLEANUP ,
28+ CONTEXT ,
29+ FLAGS ,
30+ HOST ,
31+ LView ,
32+ LViewFlags ,
33+ REACTIVE_TEMPLATE_CONSUMER ,
34+ TVIEW ,
35+ TViewType ,
36+ } from '../interfaces/view' ;
2537
2638import { getRootContext } from './view_traversal_utils' ;
2739import { getLViewParent , unwrapRNode } from './view_utils' ;
40+ import { isLView } from '../interfaces/type_checks' ;
41+ import { getFrameworkDIDebugData } from '../debug/framework_injector_profiler' ;
42+ import { Watch , WatchNode } from '@angular/core/primitives/signals/src/watch' ;
43+ import { R3Injector } from '../../di/r3_injector' ;
44+ import { ReactiveLViewConsumer } from '../reactive_lview_consumer' ;
2845
2946/**
3047 * Retrieves the component instance associated with a given DOM element.
@@ -513,3 +530,173 @@ function extractInputDebugMetadata<T>(inputs: DirectiveDef<T>['inputs']) {
513530
514531 return res ;
515532}
533+
534+ type SignalGraphNode < T > = SignalNode < T > | ComputedNode < T > | WatchNode | ReactiveLViewConsumer ;
535+
536+ interface DebugSignalNode < T > {
537+ type : 'signal' ;
538+ label : string ;
539+ value : T ;
540+ }
541+ interface DebugEffectNode {
542+ type : 'effect' ;
543+ label : string ;
544+ }
545+
546+ interface DebugComputedNode < T > {
547+ type : 'computed' ;
548+ label : string ;
549+ value : T ;
550+ }
551+
552+ interface DebugTemplateNode {
553+ type : 'template' ;
554+ label : string ;
555+ }
556+
557+ type DebugSignalGraphNode < T > =
558+ | DebugSignalNode < T >
559+ | DebugEffectNode
560+ | DebugComputedNode < T >
561+ | DebugTemplateNode ;
562+
563+ interface DebugSignalGraphEdge {
564+ from : number ;
565+ to : number ;
566+ }
567+
568+ interface DebugSignalGraph < T > {
569+ nodes : DebugSignalGraphNode < T > [ ] ;
570+ edges : DebugSignalGraphEdge [ ] ;
571+ }
572+
573+ function isComputedNode < T > ( node : SignalGraphNode < T > ) : node is ComputedNode < T > {
574+ return ( node as ComputedNode < T > ) . computation !== undefined ;
575+ }
576+
577+ function isTemplateNode < T > ( node : SignalGraphNode < T > ) : node is ReactiveLViewConsumer {
578+ return (
579+ ( node as ReactiveLViewConsumer ) . lView !== undefined &&
580+ isLView ( ( node as ReactiveLViewConsumer ) . lView )
581+ ) ;
582+ }
583+
584+ function isEffectNode < T > ( node : SignalGraphNode < T > ) : node is WatchNode {
585+ return ( node as WatchNode ) . cleanupFn !== undefined ;
586+ }
587+
588+ export function getSignalGraph ( injector : Injector ) : DebugSignalGraph < unknown > {
589+ if ( ! ( injector instanceof NodeInjector ) && ! ( injector instanceof R3Injector ) ) {
590+ return throwError ( 'getSignals must be called with a NodeInjector or an R3Injector' ) ;
591+ }
592+
593+ const signalDependenciesMap = new Map < SignalGraphNode < unknown > , Set < SignalGraphNode < unknown > > > ( ) ;
594+
595+ // if the injector is a NodeInjector, we need to extract the signals from the template
596+ // otherwise if it is an R3Injector, we proceed as normal without this extra step since both cases
597+ // require us to extract signals from the injector
598+ if ( injector instanceof NodeInjector ) {
599+ const tNode = getNodeInjectorTNode ( injector ) ! ;
600+ const lView = getNodeInjectorLView ( injector ) ;
601+
602+ assertTNode ( tNode ) ;
603+ assertLView ( lView ) ;
604+ const templateLView = lView [ tNode . index ] ;
605+ if ( templateLView ) {
606+ const templateConsumer = templateLView [ REACTIVE_TEMPLATE_CONSUMER ] ;
607+
608+ if ( templateConsumer ) {
609+ extractSignalNodesAndEdgesFromRoot ( templateConsumer , signalDependenciesMap ) ;
610+ }
611+ }
612+ }
613+
614+ const effects = extractEffectsFromInjector ( injector ) ;
615+ for ( const effect of effects ) {
616+ const { watcher} = effect ;
617+ const signalRoot = watcher [ SIGNAL ] ;
618+ extractSignalNodesAndEdgesFromRoot ( signalRoot , signalDependenciesMap ) ;
619+ }
620+
621+ return extractNodesAndEdgesFromSignalMap ( signalDependenciesMap ) ;
622+ }
623+
624+ function extractNodesAndEdgesFromSignalMap (
625+ signalMap : Map < SignalGraphNode < unknown > , Set < SignalGraphNode < unknown > > > ,
626+ ) : {
627+ nodes : DebugSignalGraphNode < unknown > [ ] ;
628+ edges : DebugSignalGraphEdge [ ] ;
629+ } {
630+ const nodes = Array . from ( signalMap . keys ( ) ) ;
631+ const debugSignalGraphNodes = nodes . map ( ( signalGraphNode : SignalGraphNode < unknown > ) => {
632+ if ( isComputedNode ( signalGraphNode ) ) {
633+ return {
634+ label : signalGraphNode . debugName ,
635+ value : signalGraphNode . value ,
636+ type : 'computed' ,
637+ } ;
638+ }
639+
640+ if ( isTemplateNode ( signalGraphNode ) ) {
641+ return {
642+ label : signalGraphNode . lView ?. [ HOST ] ?. tagName ?. toLowerCase ?.( ) ,
643+ value : undefined ,
644+ type : 'template' ,
645+ } ;
646+ }
647+
648+ if ( isEffectNode ( signalGraphNode ) ) {
649+ return {
650+ label : signalGraphNode . debugName ,
651+ value : undefined ,
652+ type : 'effect' ,
653+ } ;
654+ }
655+
656+ return {
657+ label : signalGraphNode . debugName ,
658+ value : signalGraphNode . value ,
659+ type : 'signal' ,
660+ } ;
661+ } ) as DebugSignalGraphNode < unknown > [ ] ;
662+
663+ const edges : DebugSignalGraphEdge [ ] = [ ] ;
664+ for ( const [ node , producers ] of signalMap . entries ( ) ) {
665+ for ( const producer of producers ) {
666+ edges . push ( { from : nodes . indexOf ( node ) , to : nodes . indexOf ( producer ) } ) ;
667+ }
668+ }
669+
670+ return { nodes : debugSignalGraphNodes , edges} ;
671+ }
672+
673+ function extractEffectsFromInjector ( injector : Injector ) {
674+ let diResolver : Injector | LView < unknown > = injector ;
675+ if ( injector instanceof NodeInjector ) {
676+ const lView = getNodeInjectorLView ( injector ) ! ;
677+ diResolver = lView ;
678+ }
679+
680+ const { resolverToEffects} = getFrameworkDIDebugData ( ) ;
681+ return resolverToEffects . get ( diResolver ) ?? [ ] ;
682+ }
683+
684+ function extractSignalNodesAndEdgesFromRoot (
685+ node : SignalGraphNode < unknown > ,
686+ signalDependenciesMap : Map < SignalGraphNode < unknown > , Set < SignalGraphNode < unknown > > > ,
687+ ) : Map < SignalGraphNode < unknown > , Set < SignalGraphNode < unknown > > > {
688+ if ( signalDependenciesMap . has ( node ) ) {
689+ return signalDependenciesMap ;
690+ }
691+
692+ signalDependenciesMap . set ( node , new Set ( ) ) ;
693+
694+ const { producerNode} = node ;
695+
696+ for ( const producer of producerNode ?? [ ] ) {
697+ signalDependenciesMap . get ( node ) ! . add ( producer as SignalNode < unknown > ) ;
698+ extractSignalNodesAndEdgesFromRoot ( producer as SignalNode < unknown > , signalDependenciesMap ) ;
699+ }
700+
701+ return signalDependenciesMap ;
702+ }
0 commit comments