fix(core): pipes injecting viewProviders when used on a component host node (#36512)
The flag that determines whether something should be able to inject from `viewProviders` is opt-out and the pipes weren't opted out, resulting in them being able to see the viewProviders if they're placed on a component host node. Fixes #36146. PR Close #36512
This commit is contained in:
parent
bb150c2704
commit
81d23b33ef
|
@ -71,7 +71,7 @@ import {stringifyForError} from './util/misc_utils';
|
||||||
*/
|
*/
|
||||||
let includeViewProviders = true;
|
let includeViewProviders = true;
|
||||||
|
|
||||||
function setIncludeViewProviders(v: boolean): boolean {
|
export function setIncludeViewProviders(v: boolean): boolean {
|
||||||
const oldValue = includeViewProviders;
|
const oldValue = includeViewProviders;
|
||||||
includeViewProviders = v;
|
includeViewProviders = v;
|
||||||
return oldValue;
|
return oldValue;
|
||||||
|
|
|
@ -11,6 +11,7 @@ import {PipeTransform} from '../change_detection/pipe_transform';
|
||||||
import {setInjectImplementation} from '../di/injector_compatibility';
|
import {setInjectImplementation} from '../di/injector_compatibility';
|
||||||
|
|
||||||
import {getFactoryDef} from './definition';
|
import {getFactoryDef} from './definition';
|
||||||
|
import {setIncludeViewProviders} from './di';
|
||||||
import {store, ɵɵdirectiveInject} from './instructions/all';
|
import {store, ɵɵdirectiveInject} from './instructions/all';
|
||||||
import {PipeDef, PipeDefList} from './interfaces/definition';
|
import {PipeDef, PipeDefList} from './interfaces/definition';
|
||||||
import {HEADER_OFFSET, LView, TVIEW} from './interfaces/view';
|
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 pipeFactory = pipeDef.factory || (pipeDef.factory = getFactoryDef(pipeDef.type, true));
|
||||||
const previousInjectImplementation = setInjectImplementation(ɵɵdirectiveInject);
|
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();
|
const pipeInstance = pipeFactory();
|
||||||
|
setIncludeViewProviders(previousIncludeViewProviders);
|
||||||
setInjectImplementation(previousInjectImplementation);
|
setInjectImplementation(previousInjectImplementation);
|
||||||
store(tView, getLView(), index, pipeInstance);
|
store(tView, getLView(), index, pipeInstance);
|
||||||
return pipeInstance;
|
return pipeInstance;
|
||||||
|
|
|
@ -2170,4 +2170,134 @@ describe('di', () => {
|
||||||
fixture.componentInstance.child.base,
|
fixture.componentInstance.child.base,
|
||||||
'should not get dirA from parent, but create new dirB from the useFactory provider');
|
'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<number>('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: `<child-comp [value]="'' | token"></child-comp>`,
|
||||||
|
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: `<child-comp [value]="'' | token"></child-comp>`,
|
||||||
|
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: '<child-comp dir></child-comp>',
|
||||||
|
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: '<child-comp dir></child-comp>',
|
||||||
|
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');
|
||||||
|
});
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|
Loading…
Reference in New Issue