diff --git a/packages/core/src/render3/di.ts b/packages/core/src/render3/di.ts index bba6b5cf22..54521fe6bf 100644 --- a/packages/core/src/render3/di.ts +++ b/packages/core/src/render3/di.ts @@ -321,8 +321,9 @@ export function getOrCreateInjectable( let injectorIndex = getInjectorIndex(tNode, lViewData); let parentLocation: RelativeInjectorLocation = NO_PARENT_INJECTOR; - // If we should skip this injector, start by searching the parent injector. - if (flags & InjectFlags.SkipSelf) { + // If we should skip this injector, or if there is no injector on this node, start by searching + // the parent injector. + if (injectorIndex === -1 || flags & InjectFlags.SkipSelf) { parentLocation = injectorIndex === -1 ? getParentInjectorLocation(tNode, lViewData) : lViewData[injectorIndex + PARENT_INJECTOR]; diff --git a/packages/core/test/render3/providers_spec.ts b/packages/core/test/render3/providers_spec.ts index 5519374930..67ad097d3c 100644 --- a/packages/core/test/render3/providers_spec.ts +++ b/packages/core/test/render3/providers_spec.ts @@ -8,6 +8,7 @@ import {Component as _Component, ComponentFactoryResolver, ElementRef, InjectFlags, Injectable as _Injectable, InjectionToken, InjectorType, Provider, RendererFactory2, ViewContainerRef, createInjector, defineInjectable, defineInjector, inject, ɵNgModuleDef as NgModuleDef} from '../../src/core'; import {forwardRef} from '../../src/di/forward_ref'; +import {getInjector} from '../../src/render3/discovery_utils'; import {ProvidersFeature, defineComponent, defineDirective, directiveInject, injectComponentFactoryResolver} from '../../src/render3/index'; import {bind, container, containerRefreshEnd, containerRefreshStart, element, elementEnd, elementStart, embeddedViewEnd, embeddedViewStart, interpolation1, text, textBinding} from '../../src/render3/instructions'; import {RenderFlags} from '../../src/render3/interfaces/definition'; @@ -1178,6 +1179,74 @@ describe('providers', () => { }); }); + describe('from a node without injector', () => { + abstract class Some { abstract location: String; } + + class SomeInj implements Some { + constructor(public location: String) {} + + static ngInjectableDef = defineInjectable({factory: () => new SomeInj(inject(String))}); + } + + @Component({template: `

`, providers: [{provide: String, useValue: 'From my component'}]}) + class MyComponent { + constructor() {} + + static ngComponentDef = defineComponent({ + type: MyComponent, + selectors: [['my-cmp']], + factory: () => new MyComponent(), + consts: 1, + vars: 0, + template: (rf: RenderFlags, cmp: MyComponent) => { + if (rf & RenderFlags.Create) { + element(0, 'p'); + } + }, + features: [ + ProvidersFeature([{provide: String, useValue: 'From my component'}]), + ], + }); + } + + @Component({ + template: ``, + providers: + [{provide: String, useValue: 'From app component'}, {provide: Some, useClass: SomeInj}] + }) + class AppComponent { + constructor() {} + + static ngComponentDef = defineComponent({ + type: AppComponent, + selectors: [['app-cmp']], + factory: () => new AppComponent(), + consts: 1, + vars: 0, + template: (rf: RenderFlags, cmp: AppComponent) => { + if (rf & RenderFlags.Create) { + element(0, 'my-cmp'); + } + }, + features: [ + ProvidersFeature([ + {provide: String, useValue: 'From app component'}, {provide: Some, useClass: SomeInj} + ]), + ], + directives: [MyComponent] + }); + } + + it('should work', () => { + const fixture = new ComponentFixture(AppComponent); + expect(fixture.html).toEqual('

'); + + const p = fixture.hostElement.querySelector('p'); + const injector = getInjector(p as any); + expect(injector.get(String)).toEqual('From my component'); + expect(injector.get(Some).location).toEqual('From app component'); + }); + }); }); interface ComponentTest { providers?: Provider[];