diff --git a/packages/core/src/render3/instructions/lview_debug.ts b/packages/core/src/render3/instructions/lview_debug.ts index 395d5d7b22..a30d30ced7 100644 --- a/packages/core/src/render3/instructions/lview_debug.ts +++ b/packages/core/src/render3/instructions/lview_debug.ts @@ -15,7 +15,7 @@ import {assertDefined} from '../../util/assert'; import {createNamedArrayType} from '../../util/named_array_type'; import {initNgDevMode} from '../../util/ng_dev_mode'; import {assertNodeInjector} from '../assert'; -import {getInjectorIndex} from '../di'; +import {getInjectorIndex, getParentInjectorLocation} from '../di'; import {CONTAINER_HEADER_OFFSET, HAS_TRANSPLANTED_VIEWS, LContainer, MOVED_VIEWS, NATIVE} from '../interfaces/container'; import {ComponentTemplate, DirectiveDef, DirectiveDefList, PipeDefList, ViewQueriesFunction} from '../interfaces/definition'; import {NO_PARENT_INJECTOR, NodeInjectorOffset} from '../interfaces/injector'; @@ -25,7 +25,7 @@ import {LQueries, TQueries} from '../interfaces/query'; import {Renderer3, RendererFactory3} from '../interfaces/renderer'; import {RComment, RElement, RNode} from '../interfaces/renderer_dom'; import {getTStylingRangeNext, getTStylingRangeNextDuplicate, getTStylingRangePrev, getTStylingRangePrevDuplicate, TStylingKey, TStylingRange} from '../interfaces/styling'; -import {CHILD_HEAD, CHILD_TAIL, CLEANUP, CONTEXT, DebugNode, DECLARATION_VIEW, DestroyHookData, FLAGS, HEADER_OFFSET, HookData, HOST, HostBindingOpCodes, INJECTOR, LContainerDebug as ILContainerDebug, LView, LViewDebug as ILViewDebug, LViewDebugRange, LViewDebugRangeContent, LViewFlags, NEXT, PARENT, QUERIES, RENDERER, RENDERER_FACTORY, SANITIZER, T_HOST, TData, TView as ITView, TVIEW, TView, TViewType, TViewTypeAsString} from '../interfaces/view'; +import {CHILD_HEAD, CHILD_TAIL, CLEANUP, CONTEXT, DebugNode, DECLARATION_VIEW, DestroyHookData, FLAGS, HEADER_OFFSET, HookData, HOST, HostBindingOpCodes, INJECTOR, LContainerDebug as ILContainerDebug, LView, LViewDebug as ILViewDebug, LViewDebugRange, LViewDebugRangeContent, LViewFlags, NEXT, NodeInjectorDebug, PARENT, QUERIES, RENDERER, RENDERER_FACTORY, SANITIZER, T_HOST, TData, TView as ITView, TVIEW, TView, TViewType, TViewTypeAsString} from '../interfaces/view'; import {attachDebugObject} from '../util/debug_utils'; import {getParentInjectorIndex, getParentInjectorView} from '../util/injector_utils'; import {unwrapRNode} from '../util/view_utils'; @@ -216,8 +216,20 @@ class TNode implements ITNode { debugNodeInjectorPath(lView: LView): DebugNode[] { const path: DebugNode[] = []; let injectorIndex = getInjectorIndex(this, lView); - ngDevMode && assertNodeInjector(lView, injectorIndex); + if (injectorIndex === -1) { + // Looks like the current `TNode` does not have `NodeInjector` associated with it => look for + // parent NodeInjector. + const parentLocation = getParentInjectorLocation(this, lView); + if (parentLocation !== NO_PARENT_INJECTOR) { + // We found a parent, so start searching from the parent location. + injectorIndex = getParentInjectorIndex(parentLocation); + lView = getParentInjectorView(parentLocation, lView); + } else { + // No parents have been found, so there are no `NodeInjector`s to consult. + } + } while (injectorIndex !== -1) { + ngDevMode && assertNodeInjector(lView, injectorIndex); const tNode = lView[TVIEW].data[injectorIndex + NodeInjectorOffset.TNODE] as TNode; path.push(buildDebugNode(tNode, lView)); const parentLocation = lView[injectorIndex + NodeInjectorOffset.PARENT]; @@ -583,15 +595,19 @@ export function buildDebugNode(tNode: ITNode, lView: LView): DebugNode { return { html: toHtml(native), type: toTNodeTypeAsString(tNode.type), + tNode, native: native as any, children: toDebugNodes(tNode.child, lView), factories, instances, - injector: buildNodeInjectorDebug(tNode, tView, lView) + injector: buildNodeInjectorDebug(tNode, tView, lView), + get injectorResolutionPath() { + return (tNode as TNode).debugNodeInjectorPath(lView); + }, }; } -function buildNodeInjectorDebug(tNode: ITNode, tView: ITView, lView: LView) { +function buildNodeInjectorDebug(tNode: ITNode, tView: ITView, lView: LView): NodeInjectorDebug { const viewProviders: Type[] = []; for (let i = (tNode as TNode).providerIndexStart_; i < (tNode as TNode).providerIndexEnd_; i++) { viewProviders.push(tView.data[i] as Type); @@ -633,6 +649,9 @@ function binary(array: any[], idx: number): string { * @param idx */ function toBloom(array: any[], idx: number): string { + if (idx < 0) { + return 'NO_NODE_INJECTOR'; + } return `${binary(array, idx + 7)}_${binary(array, idx + 6)}_${binary(array, idx + 5)}_${ binary(array, idx + 4)}_${binary(array, idx + 3)}_${binary(array, idx + 2)}_${ binary(array, idx + 1)}_${binary(array, idx + 0)}`; diff --git a/packages/core/src/render3/interfaces/view.ts b/packages/core/src/render3/interfaces/view.ts index 5c5bc78cf2..64e3b02052 100644 --- a/packages/core/src/render3/interfaces/view.ts +++ b/packages/core/src/render3/interfaces/view.ts @@ -1089,6 +1089,11 @@ export interface DebugNode { */ html: string|null; + /** + * Associated `TNode` + */ + tNode: TNode; + /** * Human readable node type. */ @@ -1118,6 +1123,11 @@ export interface DebugNode { * NodeInjector information. */ injector: NodeInjectorDebug; + + /** + * Injector resolution path. + */ + injectorResolutionPath: any; } export interface NodeInjectorDebug { diff --git a/packages/core/test/render3/instructions/lview_debug_spec.ts b/packages/core/test/render3/instructions/lview_debug_spec.ts index 3098ea3587..09d7fdd90e 100644 --- a/packages/core/test/render3/instructions/lview_debug_spec.ts +++ b/packages/core/test/render3/instructions/lview_debug_spec.ts @@ -6,15 +6,19 @@ * found in the LICENSE file at https://angular.io/license */ -import {ɵɵdefineComponent, ɵɵdefineDirective, ɵɵdirectiveInject, ɵɵProvidersFeature} from '@angular/core/src/core'; +import {Component, Injectable, ɵɵdefineComponent, ɵɵdefineDirective, ɵɵdirectiveInject, ɵɵProvidersFeature} from '@angular/core/src/core'; +import {getLContext} from '@angular/core/src/render3/context_discovery'; import {ɵɵelement, ɵɵelementEnd, ɵɵelementStart} from '@angular/core/src/render3/instructions/element'; import {TNodeDebug} from '@angular/core/src/render3/instructions/lview_debug'; import {createTNode, createTView} from '@angular/core/src/render3/instructions/shared'; +import {MONKEY_PATCH_KEY_NAME} from '@angular/core/src/render3/interfaces/context'; import {TNodeType} from '@angular/core/src/render3/interfaces/node'; -import {LView, TView, TViewType} from '@angular/core/src/render3/interfaces/view'; +import {LView, LViewDebug, TView, TViewType} from '@angular/core/src/render3/interfaces/view'; import {enterView, leaveView} from '@angular/core/src/render3/state'; import {insertTStylingBinding} from '@angular/core/src/render3/styling/style_binding_list'; +import {getComponentLView} from '@angular/core/src/render3/util/discovery_utils'; import {KeyValueArray} from '@angular/core/src/util/array_utils'; +import {TestBed} from '@angular/core/testing'; import {TemplateFixture} from '../render_util'; describe('lView_debug', () => { @@ -239,4 +243,62 @@ describe('lView_debug', () => { }); }); }); + + describe('debugNodeInjectorPath', () => { + @Injectable() + class MyService { + } + + @Injectable() + class OtherService { + } + + @Component({ + selector: 'parent', + template: `Parent: []`, + providers: [MyService], + }) + class ParentComponent { + } + @Component({ + selector: 'child', + template: `Child!`, + providers: [OtherService], + }) + class ChildComponent { + constructor(private myService: MyService, private otherService: OtherService) {} + } + + it('should display injection path', () => { + expect(ngDevMode).toBeTruthy(); + TestBed.configureTestingModule({declarations: [ParentComponent, ChildComponent]}); + const parentFixture = TestBed.createComponent(ParentComponent); + const parentHostElement = parentFixture.nativeElement as HTMLElement; + const childElement = parentHostElement.querySelector('child')! as HTMLElement; + if (!(childElement as any)[MONKEY_PATCH_KEY_NAME]) { + // In these browsers: + // - Chrome Mobile 72.0.3626 (Android 0.0.0) + // - IE 11.0.0 (Windows 8.1.0.0) + // Retrieving `LContext` does not work for unknown reasons, and we are unable to debug it. + // Exiting tests early to prevent breaking the test suite. + return; + } + const childLViewDebug = getComponentLView(childElement).debug!; + const parentLViewDebug = childLViewDebug.parent as LViewDebug; + const rootLViewDebug = parentLViewDebug.parent! as LViewDebug; + const childRootNode = childLViewDebug.nodes[0]; + expect(childRootNode.injector.bloom).toEqual('NO_NODE_INJECTOR'); + expect(childRootNode.injector.cumulativeBloom).toEqual('NO_NODE_INJECTOR'); + const injectorResolutionPath = childRootNode.injectorResolutionPath; + expect(injectorResolutionPath.length).toEqual(2); + expect(injectorResolutionPath[0].injector) + .toEqual( + parentLViewDebug.nodes[1].injector, + ); + expect(injectorResolutionPath[1].injector) + .toEqual( + rootLViewDebug.nodes[0].injector, + ); + }); + }); });