diff --git a/packages/core/src/render3/di.ts b/packages/core/src/render3/di.ts index 3b6eca4d6e..14a40667cd 100644 --- a/packages/core/src/render3/di.ts +++ b/packages/core/src/render3/di.ts @@ -71,7 +71,7 @@ import {stringifyForError} from './util/misc_utils'; */ let includeViewProviders = true; -function setIncludeViewProviders(v: boolean): boolean { +export function setIncludeViewProviders(v: boolean): boolean { const oldValue = includeViewProviders; includeViewProviders = v; return oldValue; diff --git a/packages/core/src/render3/pipe.ts b/packages/core/src/render3/pipe.ts index 60a4ec490e..5685c39cfb 100644 --- a/packages/core/src/render3/pipe.ts +++ b/packages/core/src/render3/pipe.ts @@ -11,6 +11,7 @@ import {PipeTransform} from '../change_detection/pipe_transform'; import {setInjectImplementation} from '../di/injector_compatibility'; import {getFactoryDef} from './definition'; +import {setIncludeViewProviders} from './di'; import {store, ɵɵdirectiveInject} from './instructions/all'; import {PipeDef, PipeDefList} from './interfaces/definition'; import {HEADER_OFFSET, LView, TVIEW} from './interfaces/view'; @@ -47,7 +48,12 @@ export function ɵɵpipe(index: number, pipeName: string): any { const pipeFactory = pipeDef.factory || (pipeDef.factory = getFactoryDef(pipeDef.type, true)); const previousInjectImplementation = setInjectImplementation(ɵɵdirectiveInject); + + // DI for pipes is supposed to behave like directives when placed on a component + // host node, which means that we have to disable access to `viewProviders`. + const previousIncludeViewProviders = setIncludeViewProviders(false); const pipeInstance = pipeFactory(); + setIncludeViewProviders(previousIncludeViewProviders); setInjectImplementation(previousInjectImplementation); store(tView, getLView(), index, pipeInstance); return pipeInstance; diff --git a/packages/core/test/acceptance/di_spec.ts b/packages/core/test/acceptance/di_spec.ts index 31e7c1a1dd..05f04e6cab 100644 --- a/packages/core/test/acceptance/di_spec.ts +++ b/packages/core/test/acceptance/di_spec.ts @@ -2170,4 +2170,134 @@ describe('di', () => { fixture.componentInstance.child.base, 'should not get dirA from parent, but create new dirB from the useFactory provider'); }); + + + describe('provider access on the same node', () => { + const token = new InjectionToken('token'); + + onlyInIvy('accessing providers on the same node through a pipe was not supported in ViewEngine') + .it('pipes should access providers from the component they are on', () => { + @Pipe({name: 'token'}) + class TokenPipe { + constructor(@Inject(token) private _token: string) {} + + transform(value: string): string { + return value + this._token; + } + } + + @Component({ + selector: 'child-comp', + template: '{{value}}', + providers: [{provide: token, useValue: 'child'}] + }) + class ChildComp { + @Input() value: any; + } + + @Component({ + template: ``, + providers: [{provide: token, useValue: 'parent'}] + }) + class App { + } + + TestBed.configureTestingModule({declarations: [App, ChildComp, TokenPipe]}); + const fixture = TestBed.createComponent(App); + fixture.detectChanges(); + + expect(fixture.nativeElement.textContent.trim()).toBe('child'); + }); + + it('pipes should not access viewProviders from the component they are on', () => { + @Pipe({name: 'token'}) + class TokenPipe { + constructor(@Inject(token) private _token: string) {} + + transform(value: string): string { + return value + this._token; + } + } + + @Component({ + selector: 'child-comp', + template: '{{value}}', + viewProviders: [{provide: token, useValue: 'child'}] + }) + class ChildComp { + @Input() value: any; + } + + @Component({ + template: ``, + viewProviders: [{provide: token, useValue: 'parent'}] + }) + class App { + } + + TestBed.configureTestingModule({declarations: [App, ChildComp, TokenPipe]}); + const fixture = TestBed.createComponent(App); + fixture.detectChanges(); + + expect(fixture.nativeElement.textContent.trim()).toBe('parent'); + }); + + it('directives should access providers from the component they are on', () => { + @Directive({selector: '[dir]'}) + class Dir { + constructor(@Inject(token) public token: string) {} + } + + @Component({ + selector: 'child-comp', + template: '', + providers: [{provide: token, useValue: 'child'}], + }) + class ChildComp { + } + + @Component({ + template: '', + providers: [{provide: token, useValue: 'parent'}] + }) + class App { + @ViewChild(Dir) dir!: Dir; + } + + TestBed.configureTestingModule({declarations: [App, ChildComp, Dir]}); + const fixture = TestBed.createComponent(App); + fixture.detectChanges(); + + expect(fixture.componentInstance.dir.token).toBe('child'); + }); + + it('directives should not access viewProviders from the component they are on', () => { + @Directive({selector: '[dir]'}) + class Dir { + constructor(@Inject(token) public token: string) {} + } + + @Component({ + selector: 'child-comp', + template: '', + viewProviders: [{provide: token, useValue: 'child'}] + }) + class ChildComp { + } + + @Component({ + template: '', + viewProviders: [{provide: token, useValue: 'parent'}] + }) + class App { + @ViewChild(Dir) dir!: Dir; + } + + TestBed.configureTestingModule({declarations: [App, ChildComp, Dir]}); + const fixture = TestBed.createComponent(App); + fixture.detectChanges(); + + expect(fixture.componentInstance.dir.token).toBe('parent'); + }); + }); });