From 368315678e9ecbb690460f685f296d0ae905d065 Mon Sep 17 00:00:00 2001 From: AleksanderBodurri Date: Mon, 7 Apr 2025 10:55:40 -0400 Subject: [PATCH] fix(core): properly handle the case where getSignalGraph is called on a componentless NodeInjector Previously this would throw an error on the assertLView when we try to discover the templateLView. Now this properly returns null for the template consumer and continues discovering other effects on the injector. --- .../core/src/render3/util/signal_debug.ts | 8 ++-- .../core/test/acceptance/signal_debug_spec.ts | 48 ++++++++++++++++++- 2 files changed, 52 insertions(+), 4 deletions(-) diff --git a/packages/core/src/render3/util/signal_debug.ts b/packages/core/src/render3/util/signal_debug.ts index 68bacfeb60a9..d9333773529f 100644 --- a/packages/core/src/render3/util/signal_debug.ts +++ b/packages/core/src/render3/util/signal_debug.ts @@ -25,6 +25,7 @@ import { SIGNAL_NODE, SignalNode, } from '../../../primitives/signals'; +import {isLView} from '../interfaces/type_checks'; export interface DebugSignalGraphNode { kind: string; @@ -79,9 +80,10 @@ function getTemplateConsumer(injector: NodeInjector): ReactiveLViewConsumer | nu const lView = getNodeInjectorLView(injector)!; assertLView(lView); const templateLView = lView[tNode.index]!; - assertLView(templateLView); - - return templateLView[REACTIVE_TEMPLATE_CONSUMER]; + if (isLView(templateLView)) { + return templateLView[REACTIVE_TEMPLATE_CONSUMER] ?? null; + } + return null; } function getNodesAndEdgesFromSignalMap(signalMap: ReadonlyMap): { diff --git a/packages/core/test/acceptance/signal_debug_spec.ts b/packages/core/test/acceptance/signal_debug_spec.ts index 3f6d86397d5b..34b55076451a 100644 --- a/packages/core/test/acceptance/signal_debug_spec.ts +++ b/packages/core/test/acceptance/signal_debug_spec.ts @@ -6,7 +6,18 @@ * found in the LICENSE file at https://angular.dev/license */ -import {Component, computed, effect, inject, Injectable, signal} from '../../src/core'; +import {NodeInjector} from '../../../core/src/render3/di'; +import {getDirectives} from '../../../core/src/render3/util/discovery_utils'; +import { + Component, + Directive, + computed, + effect, + inject, + Injectable, + signal, + Injector, +} from '../../src/core'; import { getFrameworkDIDebugData, setupFrameworkInjectorProfiler, @@ -302,4 +313,39 @@ describe('getSignalGraph', () => { producer: fourFiveSixNode, }); })); + + it('should return no nodes or edges for a NodeInjector that only has directives and no component', () => { + @Directive({ + selector: '[myDirective]', + }) + class MyDirective { + injector = inject(Injector); + } + + @Component({ + selector: 'component-with-directive', + template: `
`, + imports: [MyDirective], + }) + class WithDirective {} + + TestBed.configureTestingModule({imports: [WithDirective]}); + const fixture = TestBed.createComponent(WithDirective); + fixture.detectChanges(); + + const element = fixture.nativeElement.querySelector('#element-with-directive'); + // get the directive instance + const directiveInstances = getDirectives(element); + expect(directiveInstances.length).toBe(1); + const directiveInstance = directiveInstances[0]; + expect(directiveInstance).toBeInstanceOf(MyDirective); + const injector = (directiveInstance as MyDirective).injector; + expect(injector).toBeInstanceOf(NodeInjector); + const signalGraph = getSignalGraph(injector); + expect(signalGraph).toBeDefined(); + + const {nodes, edges} = signalGraph; + expect(nodes.length).toBe(0); + expect(edges.length).toBe(0); + }); });