diff --git a/devtools/projects/ng-devtools-backend/src/lib/BUILD.bazel b/devtools/projects/ng-devtools-backend/src/lib/BUILD.bazel index c849306bd654..1581f435e133 100644 --- a/devtools/projects/ng-devtools-backend/src/lib/BUILD.bazel +++ b/devtools/projects/ng-devtools-backend/src/lib/BUILD.bazel @@ -56,8 +56,8 @@ ts_project( name = "property_mutation", srcs = ["property-mutation.ts"], deps = [ - ":utils", "//:node_modules/@angular/core", + "//devtools/projects/ng-devtools-backend/src/lib/utils", ], ) @@ -111,35 +111,6 @@ ts_project( ], ) -ts_project( - name = "utils", - srcs = [ - "serialization-utils.ts", - "utils.ts", - ], - deps = [ - "//:node_modules/@angular/core", - "//devtools/projects/ng-devtools-backend/src/lib/ng-debug-api", - ], -) - -zoneless_web_test_suite( - name = "utils_test", - deps = [ - ":utils_test_lib", - ], -) - -ts_test_library( - name = "utils_test_lib", - srcs = [ - "serialization-utils.spec.ts", - ], - deps = [ - ":utils", - ], -) - ts_project( name = "version", srcs = ["version.ts"], @@ -174,13 +145,14 @@ ts_project( ":interfaces", ":router_tree", ":set_console_reference", - ":utils", + "//:node_modules/@angular/core", "//:node_modules/rxjs", "//devtools/projects/ng-devtools-backend/src/lib/component-inspector", "//devtools/projects/ng-devtools-backend/src/lib/component-tree", "//devtools/projects/ng-devtools-backend/src/lib/hooks", "//devtools/projects/ng-devtools-backend/src/lib/ng-debug-api", "//devtools/projects/ng-devtools-backend/src/lib/state-serializer", + "//devtools/projects/ng-devtools-backend/src/lib/utils", "//devtools/projects/protocol", "//devtools/projects/shared-utils", ], diff --git a/devtools/projects/ng-devtools-backend/src/lib/client-event-subscribers.ts b/devtools/projects/ng-devtools-backend/src/lib/client-event-subscribers.ts index ef7a8f4a3fcb..681bddd99946 100644 --- a/devtools/projects/ng-devtools-backend/src/lib/client-event-subscribers.ts +++ b/devtools/projects/ng-devtools-backend/src/lib/client-event-subscribers.ts @@ -6,6 +6,7 @@ * found in the LICENSE file at https://angular.dev/license */ +import {ɵDebugSignalGraph} from '@angular/core'; import { ComponentExplorerViewQuery, ComponentType, @@ -57,10 +58,11 @@ import {getRouterCallableConstructRef, parseRoutes, RoutePropertyType} from './r import {ngDebugClient, ngDebugDependencyInjectionApiIsSupported} from './ng-debug-api/ng-debug-api'; import {setConsoleReference} from './set-console-reference'; import {serializeDirectiveState, serializeValue} from './state-serializer/state-serializer'; -import {runOutsideAngular, unwrapSignal} from './utils'; +import {runOutsideAngular, unwrapSignal} from './utils/general'; import {DirectiveForestHooks} from './hooks/hooks'; import {getSupportedApis} from './ng-debug-api/supported-apis'; -import {sanitizeObject} from './serialization-utils'; +import {sanitizeObject} from './utils/serialization'; +import {SignalGraphRef} from './utils/signal-graph-ref'; type InspectorRef = {ref: ComponentInspector | null}; @@ -262,7 +264,21 @@ const getSignalNestedPropertiesCallback = const ng = ngDebugClient(); - const signalGraph = ng.ɵgetSignalGraph?.(injector); + let signalGraph: ɵDebugSignalGraph | undefined; + + // Considering that the inspection of signal value nested properties + // usually involves multiple requests, we store the signal graph + // during the first call. We keep only the last requested signal graph + // to avoid filling the heap with graphs that may not be needed. + if (componentSignalGraphRef.exists(node.nativeElement!)) { + signalGraph = componentSignalGraphRef.deref(node.nativeElement!); + } else { + signalGraph = ng.ɵgetSignalGraph?.(injector); + if (signalGraph) { + componentSignalGraphRef.set(node.nativeElement!, signalGraph); + } + } + if (!signalGraph) { return emitEmpty(); } @@ -493,7 +509,7 @@ const getInjectorProvidersCallback = const serializedProviderRecords: SerializedProviderRecord[] = []; - for (const [token, records] of tokenToRecords.entries()) { + for (const [, records] of tokenToRecords.entries()) { const multiRecords = records.filter((record) => record.multi); const nonMultiRecords = records.filter((record) => !record.multi); @@ -617,6 +633,10 @@ const getInjectorInstance = ( }; const getSignalGraphCallback = (messageBus: MessageBus) => (element: ElementPosition) => { + // We assume that a new request for a signal graph + // should invalidate the current ref cache. + componentSignalGraphRef.clear(); + const ng = ngDebugClient(); // get injector from position @@ -664,3 +684,13 @@ export function sanitizeRouteData(route: Route): Route { return route; } + +/** + * Keeps a reference to the last requested signal graph. + * This should save us from needlessly calling `ng.ɵgetSignalGraph` + * when we are still managing the same/last graph (e.g. inspecting + * signal value nested properties). The ref is tied to the host element. + * + * Note: If the element is destroyed, the graph is garbage collected. + */ +const componentSignalGraphRef = new SignalGraphRef(); diff --git a/devtools/projects/ng-devtools-backend/src/lib/component-inspector/BUILD.bazel b/devtools/projects/ng-devtools-backend/src/lib/component-inspector/BUILD.bazel index 53226669e92d..1a2169d23291 100644 --- a/devtools/projects/ng-devtools-backend/src/lib/component-inspector/BUILD.bazel +++ b/devtools/projects/ng-devtools-backend/src/lib/component-inspector/BUILD.bazel @@ -24,9 +24,9 @@ ts_test_library( "//:node_modules/@angular/core", "//devtools/projects/ng-devtools-backend/src/lib:highlighter", "//devtools/projects/ng-devtools-backend/src/lib:interfaces", - "//devtools/projects/ng-devtools-backend/src/lib:utils", "//devtools/projects/ng-devtools-backend/src/lib/component-tree", "//devtools/projects/ng-devtools-backend/src/lib/hooks", + "//devtools/projects/ng-devtools-backend/src/lib/utils", "//devtools/projects/protocol", ], ) diff --git a/devtools/projects/ng-devtools-backend/src/lib/component-tree/BUILD.bazel b/devtools/projects/ng-devtools-backend/src/lib/component-tree/BUILD.bazel index 0a8da72fb1c4..7f494380261b 100644 --- a/devtools/projects/ng-devtools-backend/src/lib/component-tree/BUILD.bazel +++ b/devtools/projects/ng-devtools-backend/src/lib/component-tree/BUILD.bazel @@ -21,10 +21,10 @@ ts_project( "//:node_modules/@angular/core", "//devtools/projects/ng-devtools-backend/src/lib:interfaces", "//devtools/projects/ng-devtools-backend/src/lib:property_mutation", - "//devtools/projects/ng-devtools-backend/src/lib:utils", "//devtools/projects/ng-devtools-backend/src/lib/directive-forest", "//devtools/projects/ng-devtools-backend/src/lib/ng-debug-api", "//devtools/projects/ng-devtools-backend/src/lib/state-serializer", + "//devtools/projects/ng-devtools-backend/src/lib/utils", "//devtools/projects/protocol", ], ) diff --git a/devtools/projects/ng-devtools-backend/src/lib/component-tree/component-tree.ts b/devtools/projects/ng-devtools-backend/src/lib/component-tree/component-tree.ts index fb6012f417f4..cebe604fd5eb 100644 --- a/devtools/projects/ng-devtools-backend/src/lib/component-tree/component-tree.ts +++ b/devtools/projects/ng-devtools-backend/src/lib/component-tree/component-tree.ts @@ -49,7 +49,7 @@ import {mutateNestedProp} from '../property-mutation'; import {ComponentTreeNode, DirectiveInstanceType, ComponentInstanceType} from '../interfaces'; import {getAppRoots} from './get-roots'; import {AcxChangeDetectionStrategy, ChangeDetectionStrategy, Framework} from './core-enums'; -import {unwrapSignal} from '../utils'; +import {unwrapSignal} from '../utils/general'; export const injectorToId = new WeakMap(); export const nodeInjectorToResolutionPath = new WeakMap(); diff --git a/devtools/projects/ng-devtools-backend/src/lib/directive-forest/BUILD.bazel b/devtools/projects/ng-devtools-backend/src/lib/directive-forest/BUILD.bazel index c998453c6dcb..8f972bfd63a5 100644 --- a/devtools/projects/ng-devtools-backend/src/lib/directive-forest/BUILD.bazel +++ b/devtools/projects/ng-devtools-backend/src/lib/directive-forest/BUILD.bazel @@ -14,10 +14,10 @@ ts_project( "//:node_modules/semver", "//devtools/projects/ng-devtools-backend/src/lib:highlighter", "//devtools/projects/ng-devtools-backend/src/lib:interfaces", - "//devtools/projects/ng-devtools-backend/src/lib:utils", "//devtools/projects/ng-devtools-backend/src/lib:version", "//devtools/projects/ng-devtools-backend/src/lib/ng-debug-api", "//devtools/projects/ng-devtools-backend/src/lib/state-serializer", + "//devtools/projects/ng-devtools-backend/src/lib/utils", "//devtools/projects/protocol", ], ) @@ -31,7 +31,7 @@ ts_test_library( ":directive-forest", "//:node_modules/@angular/core", "//devtools/projects/ng-devtools-backend/src/lib:interfaces", - "//devtools/projects/ng-devtools-backend/src/lib:utils", + "//devtools/projects/ng-devtools-backend/src/lib/utils", ], ) diff --git a/devtools/projects/ng-devtools-backend/src/lib/directive-forest/ltree.ts b/devtools/projects/ng-devtools-backend/src/lib/directive-forest/ltree.ts index 19ef2b9199a2..af57206c52d3 100644 --- a/devtools/projects/ng-devtools-backend/src/lib/directive-forest/ltree.ts +++ b/devtools/projects/ng-devtools-backend/src/lib/directive-forest/ltree.ts @@ -10,7 +10,7 @@ import semver from 'semver'; import {getDirectiveName} from '../highlighter'; import {ComponentInstanceType, ComponentTreeNode, DirectiveInstanceType} from '../interfaces'; -import {isCustomElement} from '../utils'; +import {isCustomElement} from '../utils/general'; import {VERSION} from '../version'; let HEADER_OFFSET = 19; diff --git a/devtools/projects/ng-devtools-backend/src/lib/directive-forest/render-tree.ts b/devtools/projects/ng-devtools-backend/src/lib/directive-forest/render-tree.ts index e4d117e94f9f..eb2e7fb24609 100644 --- a/devtools/projects/ng-devtools-backend/src/lib/directive-forest/render-tree.ts +++ b/devtools/projects/ng-devtools-backend/src/lib/directive-forest/render-tree.ts @@ -14,7 +14,7 @@ import {HydrationStatus} from '../../../../protocol'; import {ComponentTreeNode} from '../interfaces'; import {ngDebugClient} from '../ng-debug-api/ng-debug-api'; -import {isCustomElement} from '../utils'; +import {isCustomElement} from '../utils/general'; import { ControlFlowBlocksIterator, createControlFlowTreeNode, diff --git a/devtools/projects/ng-devtools-backend/src/lib/hooks/BUILD.bazel b/devtools/projects/ng-devtools-backend/src/lib/hooks/BUILD.bazel index 0f738e23487c..29e1936ed211 100644 --- a/devtools/projects/ng-devtools-backend/src/lib/hooks/BUILD.bazel +++ b/devtools/projects/ng-devtools-backend/src/lib/hooks/BUILD.bazel @@ -15,8 +15,8 @@ ts_project( ":identity_tracker", "//devtools/projects/ng-devtools-backend/src/lib:highlighter", "//devtools/projects/ng-devtools-backend/src/lib:interfaces", - "//devtools/projects/ng-devtools-backend/src/lib:utils", "//devtools/projects/ng-devtools-backend/src/lib/hooks/profiler", + "//devtools/projects/ng-devtools-backend/src/lib/utils", "//devtools/projects/protocol", ], ) diff --git a/devtools/projects/ng-devtools-backend/src/lib/hooks/capture.ts b/devtools/projects/ng-devtools-backend/src/lib/hooks/capture.ts index b222aef8f1d3..dc23aab837d5 100644 --- a/devtools/projects/ng-devtools-backend/src/lib/hooks/capture.ts +++ b/devtools/projects/ng-devtools-backend/src/lib/hooks/capture.ts @@ -17,7 +17,7 @@ import { import {getDirectiveName} from '../highlighter'; import {ComponentTreeNode} from '../interfaces'; -import {isCustomElement, runOutsideAngular} from '../utils'; +import {isCustomElement, runOutsideAngular} from '../utils/general'; import {initializeOrGetDirectiveForestHooks} from '.'; import {DirectiveForestHooks} from './hooks'; diff --git a/devtools/projects/ng-devtools-backend/src/lib/hooks/profiler/BUILD.bazel b/devtools/projects/ng-devtools-backend/src/lib/hooks/profiler/BUILD.bazel index 7c513a05daf3..07dea802c5ac 100644 --- a/devtools/projects/ng-devtools-backend/src/lib/hooks/profiler/BUILD.bazel +++ b/devtools/projects/ng-devtools-backend/src/lib/hooks/profiler/BUILD.bazel @@ -11,10 +11,10 @@ ts_project( deps = [ "//:node_modules/@angular/core", "//:node_modules/rxjs", - "//devtools/projects/ng-devtools-backend/src/lib:utils", "//devtools/projects/ng-devtools-backend/src/lib/directive-forest", "//devtools/projects/ng-devtools-backend/src/lib/hooks:identity_tracker", "//devtools/projects/ng-devtools-backend/src/lib/ng-debug-api", + "//devtools/projects/ng-devtools-backend/src/lib/utils", "//devtools/projects/protocol", ], ) diff --git a/devtools/projects/ng-devtools-backend/src/lib/hooks/profiler/native.ts b/devtools/projects/ng-devtools-backend/src/lib/hooks/profiler/native.ts index 79064b4f161d..6c415a0599ac 100644 --- a/devtools/projects/ng-devtools-backend/src/lib/hooks/profiler/native.ts +++ b/devtools/projects/ng-devtools-backend/src/lib/hooks/profiler/native.ts @@ -9,7 +9,7 @@ import {ɵProfilerEvent} from '@angular/core'; import {getDirectiveHostElement} from '../../directive-forest'; import {ngDebugClient} from '../../ng-debug-api/ng-debug-api'; -import {runOutsideAngular} from '../../utils'; +import {runOutsideAngular} from '../../utils/general'; import {IdentityTracker, NodeArray} from '../identity-tracker'; import {getLifeCycleName, Hooks, Profiler} from './shared'; diff --git a/devtools/projects/ng-devtools-backend/src/lib/hooks/profiler/polyfill.ts b/devtools/projects/ng-devtools-backend/src/lib/hooks/profiler/polyfill.ts index c27330bcae02..837dabe6718c 100644 --- a/devtools/projects/ng-devtools-backend/src/lib/hooks/profiler/polyfill.ts +++ b/devtools/projects/ng-devtools-backend/src/lib/hooks/profiler/polyfill.ts @@ -11,7 +11,7 @@ import { getLViewFromDirectiveOrElementInstance, METADATA_PROPERTY_NAME, } from '../../directive-forest'; -import {runOutsideAngular} from '../../utils'; +import {runOutsideAngular} from '../../utils/general'; import {IdentityTracker, NodeArray} from '../identity-tracker'; import {getLifeCycleName, Profiler} from './shared'; diff --git a/devtools/projects/ng-devtools-backend/src/lib/property-mutation.ts b/devtools/projects/ng-devtools-backend/src/lib/property-mutation.ts index be991938ae3d..cf77c0f43b28 100644 --- a/devtools/projects/ng-devtools-backend/src/lib/property-mutation.ts +++ b/devtools/projects/ng-devtools-backend/src/lib/property-mutation.ts @@ -7,7 +7,7 @@ */ import type {Signal, WritableSignal} from '@angular/core'; -import {isSignal} from './utils'; +import {isSignal} from './utils/general'; /** Represents a property access operation. */ interface PropertyAccess { diff --git a/devtools/projects/ng-devtools-backend/src/lib/state-serializer/BUILD.bazel b/devtools/projects/ng-devtools-backend/src/lib/state-serializer/BUILD.bazel index 188f16a5a000..d633a4456dbc 100644 --- a/devtools/projects/ng-devtools-backend/src/lib/state-serializer/BUILD.bazel +++ b/devtools/projects/ng-devtools-backend/src/lib/state-serializer/BUILD.bazel @@ -9,7 +9,7 @@ ng_project( exclude = ["*.spec.ts"], ), deps = [ - "//devtools/projects/ng-devtools-backend/src/lib:utils", + "//devtools/projects/ng-devtools-backend/src/lib/utils", "//devtools/projects/protocol", ], ) diff --git a/devtools/projects/ng-devtools-backend/src/lib/state-serializer/object-utils.ts b/devtools/projects/ng-devtools-backend/src/lib/state-serializer/object-utils.ts index 3a9532363cee..231a195f0459 100644 --- a/devtools/projects/ng-devtools-backend/src/lib/state-serializer/object-utils.ts +++ b/devtools/projects/ng-devtools-backend/src/lib/state-serializer/object-utils.ts @@ -6,7 +6,7 @@ * found in the LICENSE file at https://angular.dev/license */ -import {unwrapSignal} from '../utils'; +import {unwrapSignal} from '../utils/general'; // Intentionally do not include all the prototype (Except for getters and setters) // because it contains inherited methods (hasOwnProperty, etc.). Also ignore symbols diff --git a/devtools/projects/ng-devtools-backend/src/lib/state-serializer/prop-type.ts b/devtools/projects/ng-devtools-backend/src/lib/state-serializer/prop-type.ts index e1f3972ec72e..f97718d34c51 100644 --- a/devtools/projects/ng-devtools-backend/src/lib/state-serializer/prop-type.ts +++ b/devtools/projects/ng-devtools-backend/src/lib/state-serializer/prop-type.ts @@ -8,7 +8,7 @@ import {PropType} from '../../../../protocol'; -import {isSignal, safelyReadSignalValue} from '../utils'; +import {isSignal, safelyReadSignalValue} from '../utils/general'; const commonTypes = { boolean: PropType.Boolean, diff --git a/devtools/projects/ng-devtools-backend/src/lib/state-serializer/serialized-descriptor-factory.ts b/devtools/projects/ng-devtools-backend/src/lib/state-serializer/serialized-descriptor-factory.ts index 40bf8a47020d..89b1249fadc7 100644 --- a/devtools/projects/ng-devtools-backend/src/lib/state-serializer/serialized-descriptor-factory.ts +++ b/devtools/projects/ng-devtools-backend/src/lib/state-serializer/serialized-descriptor-factory.ts @@ -8,7 +8,7 @@ import {ContainerType, Descriptor, NestedProp, PropType} from '../../../../protocol'; -import {isSignal, safelyReadSignalValue, unwrapSignal} from '../utils'; +import {isSignal, safelyReadSignalValue, unwrapSignal} from '../utils/general'; import {getDescriptor, getKeys} from './object-utils'; diff --git a/devtools/projects/ng-devtools-backend/src/lib/state-serializer/state-serializer.ts b/devtools/projects/ng-devtools-backend/src/lib/state-serializer/state-serializer.ts index e8da6552e509..2813d7d501a7 100644 --- a/devtools/projects/ng-devtools-backend/src/lib/state-serializer/state-serializer.ts +++ b/devtools/projects/ng-devtools-backend/src/lib/state-serializer/state-serializer.ts @@ -8,7 +8,7 @@ import {ContainerType, Descriptor, NestedProp, PropType} from '../../../../protocol'; -import {isSignal, unwrapSignal} from '../utils'; +import {isSignal, unwrapSignal} from '../utils/general'; import {getKeys} from './object-utils'; import {getPropType} from './prop-type'; diff --git a/devtools/projects/ng-devtools-backend/src/lib/utils/BUILD.bazel b/devtools/projects/ng-devtools-backend/src/lib/utils/BUILD.bazel new file mode 100644 index 000000000000..f4fecdf98412 --- /dev/null +++ b/devtools/projects/ng-devtools-backend/src/lib/utils/BUILD.bazel @@ -0,0 +1,34 @@ +load("//devtools/tools:defaults.bzl", "ng_project", "ts_test_library", "zoneless_web_test_suite") + +package(default_visibility = ["//devtools:__subpackages__"]) + +ng_project( + name = "utils", + srcs = glob( + include = ["*.ts"], + exclude = ["*.spec.ts"], + ), + deps = [ + "//:node_modules/@angular/core", + "//devtools/projects/ng-devtools-backend/src/lib/ng-debug-api", + ], +) + +ts_test_library( + name = "test_lib", + srcs = [ + "serialization.spec.ts", + "signal-graph-ref.spec.ts", + ], + deps = [ + ":utils", + "//:node_modules/@angular/core", + ], +) + +zoneless_web_test_suite( + name = "test", + deps = [ + ":test_lib", + ], +) diff --git a/devtools/projects/ng-devtools-backend/src/lib/utils.ts b/devtools/projects/ng-devtools-backend/src/lib/utils/general.ts similarity index 94% rename from devtools/projects/ng-devtools-backend/src/lib/utils.ts rename to devtools/projects/ng-devtools-backend/src/lib/utils/general.ts index 087ff34cb572..ba4ca32f5406 100644 --- a/devtools/projects/ng-devtools-backend/src/lib/utils.ts +++ b/devtools/projects/ng-devtools-backend/src/lib/utils/general.ts @@ -6,7 +6,7 @@ * found in the LICENSE file at https://angular.dev/license */ -import {ngDebugApiIsSupported, ngDebugClient} from './ng-debug-api/ng-debug-api'; +import {ngDebugApiIsSupported, ngDebugClient} from '../ng-debug-api/ng-debug-api'; export const runOutsideAngular = (f: () => void): void => { const w = window as any; diff --git a/devtools/projects/ng-devtools-backend/src/lib/serialization-utils.spec.ts b/devtools/projects/ng-devtools-backend/src/lib/utils/serialization.spec.ts similarity index 96% rename from devtools/projects/ng-devtools-backend/src/lib/serialization-utils.spec.ts rename to devtools/projects/ng-devtools-backend/src/lib/utils/serialization.spec.ts index 99c1c82d330b..0780d47ce1e2 100644 --- a/devtools/projects/ng-devtools-backend/src/lib/serialization-utils.spec.ts +++ b/devtools/projects/ng-devtools-backend/src/lib/utils/serialization.spec.ts @@ -6,7 +6,7 @@ * found in the LICENSE file at https://angular.dev/license */ -import {sanitizeObject} from './serialization-utils'; +import {sanitizeObject} from './serialization'; describe('sanitizeObject', () => { it('should not change valid object', () => { diff --git a/devtools/projects/ng-devtools-backend/src/lib/serialization-utils.ts b/devtools/projects/ng-devtools-backend/src/lib/utils/serialization.ts similarity index 100% rename from devtools/projects/ng-devtools-backend/src/lib/serialization-utils.ts rename to devtools/projects/ng-devtools-backend/src/lib/utils/serialization.ts diff --git a/devtools/projects/ng-devtools-backend/src/lib/utils/signal-graph-ref.spec.ts b/devtools/projects/ng-devtools-backend/src/lib/utils/signal-graph-ref.spec.ts new file mode 100644 index 000000000000..115a43822925 --- /dev/null +++ b/devtools/projects/ng-devtools-backend/src/lib/utils/signal-graph-ref.spec.ts @@ -0,0 +1,41 @@ +/** + * @license + * Copyright Google LLC All Rights Reserved. + * + * Use of this source code is governed by an MIT-style license that can be + * found in the LICENSE file at https://angular.dev/license + */ + +import {ɵDebugSignalGraph} from '@angular/core'; +import {SignalGraphRef} from './signal-graph-ref'; + +describe('SignalGraphRef', () => { + let key: object; + let graph: ɵDebugSignalGraph; + let ref: SignalGraphRef; + + beforeEach(() => { + key = {}; + graph = {} as ɵDebugSignalGraph; + ref = new SignalGraphRef(); + }); + + it('should store a ref', () => { + ref.set(key, graph); + + expect(ref.deref(key)).toBe(graph); + }); + + it('should clear a ref', () => { + ref.set(key, graph); + ref.clear(); + + expect(ref.deref(key)).toBe(undefined); + }); + + it('should tell if a ref exists', () => { + ref.set(key, graph); + + expect(ref.exists(key)).toBe(true); + }); +}); diff --git a/devtools/projects/ng-devtools-backend/src/lib/utils/signal-graph-ref.ts b/devtools/projects/ng-devtools-backend/src/lib/utils/signal-graph-ref.ts new file mode 100644 index 000000000000..606e257ee18f --- /dev/null +++ b/devtools/projects/ng-devtools-backend/src/lib/utils/signal-graph-ref.ts @@ -0,0 +1,37 @@ +/** + * @license + * Copyright Google LLC All Rights Reserved. + * + * Use of this source code is governed by an MIT-style license that can be + * found in the LICENSE file at https://angular.dev/license + */ + +import {ɵDebugSignalGraph as InternalDebugSignalGraph} from '@angular/core'; + +/** + * Keeps a `ɵDebugSignalGraph` reference tied to a weak-referenced key. + * If the key is garbage collected, the graph is also cleared. + */ +export class SignalGraphRef { + private ref: WeakMap = new WeakMap(); + + /** Gets the key-bound signal graph reference. */ + deref(key: T): InternalDebugSignalGraph | undefined { + return this.ref.get(key); + } + + /** Sets the signal graph. */ + set(key: T, graph: InternalDebugSignalGraph) { + this.ref = new WeakMap([[key, graph]]); + } + + /** Checks if the key-bound signal graph exists. */ + exists(key: T) { + return !!this.ref.get(key); + } + + /** Clears the reference. */ + clear() { + this.ref = new WeakMap(); + } +} diff --git a/packages/core/src/core_render3_private_export.ts b/packages/core/src/core_render3_private_export.ts index c455bc48a8e3..5f0152bb3540 100644 --- a/packages/core/src/core_render3_private_export.ts +++ b/packages/core/src/core_render3_private_export.ts @@ -291,6 +291,11 @@ export { FrameworkAgnosticGlobalUtils as ɵFrameworkAgnosticGlobalUtils, GlobalDevModeUtils as ɵGlobalDevModeUtils, } from './render3/util/global_utils'; +export { + DebugSignalGraph as ɵDebugSignalGraph, + DebugSignalGraphEdge as ɵDebugSignalGraphEdge, + DebugSignalGraphNode as ɵDebugSignalGraphNode, +} from './render3/util/signal_debug'; export {getTransferState as ɵgetTransferState} from './render3/util/transfer_state_utils'; export { isViewDirty as ɵisViewDirty,