diff --git a/packages/core/src/render3/instructions/shared.ts b/packages/core/src/render3/instructions/shared.ts index 6bf616c199674c..145bf73cefacbd 100644 --- a/packages/core/src/render3/instructions/shared.ts +++ b/packages/core/src/render3/instructions/shared.ts @@ -1218,6 +1218,16 @@ function instantiateAllDirectives( tView: TView, lView: LView, tNode: TDirectiveHostNode, native: RNode) { const start = tNode.directiveStart; const end = tNode.directiveEnd; + + // The component view needs to be created before creating the node injector + // since it is used to inject some special symbols like `ChangeDetectorRef`. + if (isComponentHost(tNode)) { + ngDevMode && assertTNodeType(tNode, TNodeType.AnyRNode); + addComponentLogic( + lView, tNode as TElementNode, + tView.data[start + tNode.componentOffset] as ComponentDef); + } + if (!tView.firstCreatePass) { getOrCreateNodeInjectorForNode(tNode, lView); } @@ -1227,13 +1237,6 @@ function instantiateAllDirectives( const initialInputs = tNode.initialInputs; for (let i = start; i < end; i++) { const def = tView.data[i] as DirectiveDef; - const isComponent = isComponentDef(def); - - if (isComponent) { - ngDevMode && assertTNodeType(tNode, TNodeType.AnyRNode); - addComponentLogic(lView, tNode as TElementNode, def as ComponentDef); - } - const directive = getNodeInjectable(lView, tView, i, tNode); attachPatchData(directive, lView); @@ -1241,9 +1244,9 @@ function instantiateAllDirectives( setInputsFromAttrs(lView, i - start, directive, def, tNode, initialInputs!); } - if (isComponent) { + if (isComponentDef(def)) { const componentView = getComponentLViewByIndex(tNode.index, lView); - componentView[CONTEXT] = directive; + componentView[CONTEXT] = getNodeInjectable(lView, tView, i, tNode); } } } diff --git a/packages/core/test/acceptance/host_directives_spec.ts b/packages/core/test/acceptance/host_directives_spec.ts index 828f7bd8e582b0..85b6a951814d84 100644 --- a/packages/core/test/acceptance/host_directives_spec.ts +++ b/packages/core/test/acceptance/host_directives_spec.ts @@ -6,7 +6,7 @@ * found in the LICENSE file at https://angular.io/license */ -import {AfterViewChecked, AfterViewInit, Component, Directive, ElementRef, EventEmitter, forwardRef, inject, Inject, InjectionToken, Input, OnChanges, OnInit, Output, SimpleChanges, Type, ViewChild, ViewContainerRef} from '@angular/core'; +import {AfterViewChecked, AfterViewInit, ChangeDetectorRef, Component, Directive, ElementRef, EventEmitter, forwardRef, inject, Inject, InjectionToken, Input, OnChanges, OnInit, Output, SimpleChanges, Type, ViewChild, ViewContainerRef} from '@angular/core'; import {TestBed} from '@angular/core/testing'; import {By} from '@angular/platform-browser'; @@ -919,6 +919,44 @@ describe('host directives', () => { expect(() => TestBed.createComponent(App)) .toThrowError(/NG0200: Circular dependency in DI detected for HostDir/); }); + + it('should inject a valid ChangeDetectorRef when attached to a component', () => { + type InternalChangeDetectorRef = ChangeDetectorRef&{_lView: unknown}; + + @Directive({standalone: true}) + class HostDir { + changeDetectorRef = inject(ChangeDetectorRef) as InternalChangeDetectorRef; + } + + @Component({selector: 'my-comp', hostDirectives: [HostDir], template: ''}) + class Comp { + changeDetectorRef = inject(ChangeDetectorRef) as InternalChangeDetectorRef; + } + + @Component({template: ''}) + class App { + @ViewChild(HostDir) hostDir!: HostDir; + @ViewChild(Comp) comp!: Comp; + } + + TestBed.configureTestingModule({declarations: [App, Comp]}); + const fixture = TestBed.createComponent(App); + fixture.detectChanges(); + + const hostDirectiveCdr = fixture.componentInstance.hostDir.changeDetectorRef; + const componentCdr = fixture.componentInstance.comp.changeDetectorRef; + + // We can't assert that the change detectors are the same by comparing + // them directly, because a new one is created each time. Instead of we + // compare that they're associated with the same LView. + expect(hostDirectiveCdr._lView).toBeTruthy(); + expect(componentCdr._lView).toBeTruthy(); + expect(hostDirectiveCdr._lView).toBe(componentCdr._lView); + expect(() => { + hostDirectiveCdr.markForCheck(); + hostDirectiveCdr.detectChanges(); + }).not.toThrow(); + }); }); describe('outputs', () => {