fix(ivy): should not throw when getting VCRef.parentInjector on the root view (#27909)
PR Close #27909
This commit is contained in:
parent
fb7816fed4
commit
929334b0bf
|
@ -208,7 +208,7 @@ export function getParentInjectorLocation(tNode: TNode, view: LView): RelativeIn
|
||||||
let viewOffset = 1;
|
let viewOffset = 1;
|
||||||
while (hostTNode && hostTNode.injectorIndex === -1) {
|
while (hostTNode && hostTNode.injectorIndex === -1) {
|
||||||
view = view[DECLARATION_VIEW] !;
|
view = view[DECLARATION_VIEW] !;
|
||||||
hostTNode = view[HOST_NODE] !;
|
hostTNode = view ? view[HOST_NODE] : null;
|
||||||
viewOffset++;
|
viewOffset++;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -292,82 +292,86 @@ export function injectAttributeImpl(tNode: TNode, attrNameToInject: string): str
|
||||||
* @returns the value from the injector, `null` when not found, or `notFoundValue` if provided
|
* @returns the value from the injector, `null` when not found, or `notFoundValue` if provided
|
||||||
*/
|
*/
|
||||||
export function getOrCreateInjectable<T>(
|
export function getOrCreateInjectable<T>(
|
||||||
tNode: TElementNode | TContainerNode | TElementContainerNode, lView: LView,
|
tNode: TElementNode | TContainerNode | TElementContainerNode | null, lView: LView,
|
||||||
token: Type<T>| InjectionToken<T>, flags: InjectFlags = InjectFlags.Default,
|
token: Type<T>| InjectionToken<T>, flags: InjectFlags = InjectFlags.Default,
|
||||||
notFoundValue?: any): T|null {
|
notFoundValue?: any): T|null {
|
||||||
const bloomHash = bloomHashBitOrFactory(token);
|
if (tNode) {
|
||||||
// If the ID stored here is a function, this is a special object like ElementRef or TemplateRef
|
const bloomHash = bloomHashBitOrFactory(token);
|
||||||
// so just call the factory function to create it.
|
// If the ID stored here is a function, this is a special object like ElementRef or TemplateRef
|
||||||
if (typeof bloomHash === 'function') {
|
// so just call the factory function to create it.
|
||||||
const savePreviousOrParentTNode = getPreviousOrParentTNode();
|
if (typeof bloomHash === 'function') {
|
||||||
const saveLView = getLView();
|
const savePreviousOrParentTNode = getPreviousOrParentTNode();
|
||||||
setTNodeAndViewData(tNode, lView);
|
const saveLView = getLView();
|
||||||
try {
|
setTNodeAndViewData(tNode, lView);
|
||||||
const value = bloomHash();
|
try {
|
||||||
if (value == null && !(flags & InjectFlags.Optional)) {
|
const value = bloomHash();
|
||||||
throw new Error(`No provider for ${stringify(token)}!`);
|
if (value == null && !(flags & InjectFlags.Optional)) {
|
||||||
} else {
|
throw new Error(`No provider for ${stringify(token)}!`);
|
||||||
return value;
|
} else {
|
||||||
|
return value;
|
||||||
|
}
|
||||||
|
} finally {
|
||||||
|
setTNodeAndViewData(savePreviousOrParentTNode, saveLView);
|
||||||
}
|
}
|
||||||
} finally {
|
} else if (typeof bloomHash == 'number') {
|
||||||
setTNodeAndViewData(savePreviousOrParentTNode, saveLView);
|
// If the token has a bloom hash, then it is a token which could be in NodeInjector.
|
||||||
}
|
|
||||||
} else if (typeof bloomHash == 'number') {
|
|
||||||
// If the token has a bloom hash, then it is a token which could be in NodeInjector.
|
|
||||||
|
|
||||||
// A reference to the previous injector TView that was found while climbing the element injector
|
// A reference to the previous injector TView that was found while climbing the element
|
||||||
// tree. This is used to know if viewProviders can be accessed on the current injector.
|
// injector tree. This is used to know if viewProviders can be accessed on the current
|
||||||
let previousTView: TView|null = null;
|
// injector.
|
||||||
let injectorIndex = getInjectorIndex(tNode, lView);
|
let previousTView: TView|null = null;
|
||||||
let parentLocation: RelativeInjectorLocation = NO_PARENT_INJECTOR;
|
let injectorIndex = getInjectorIndex(tNode, lView);
|
||||||
let hostTElementNode: TNode|null =
|
let parentLocation: RelativeInjectorLocation = NO_PARENT_INJECTOR;
|
||||||
flags & InjectFlags.Host ? findComponentView(lView)[HOST_NODE] : null;
|
let hostTElementNode: TNode|null =
|
||||||
|
flags & InjectFlags.Host ? findComponentView(lView)[HOST_NODE] : null;
|
||||||
|
|
||||||
// If we should skip this injector, or if there is no injector on this node, start by searching
|
// If we should skip this injector, or if there is no injector on this node, start by
|
||||||
// the parent injector.
|
// searching
|
||||||
if (injectorIndex === -1 || flags & InjectFlags.SkipSelf) {
|
// the parent injector.
|
||||||
parentLocation = injectorIndex === -1 ? getParentInjectorLocation(tNode, lView) :
|
if (injectorIndex === -1 || flags & InjectFlags.SkipSelf) {
|
||||||
lView[injectorIndex + PARENT_INJECTOR];
|
parentLocation = injectorIndex === -1 ? getParentInjectorLocation(tNode, lView) :
|
||||||
|
lView[injectorIndex + PARENT_INJECTOR];
|
||||||
|
|
||||||
if (!shouldSearchParent(flags, false)) {
|
if (!shouldSearchParent(flags, false)) {
|
||||||
injectorIndex = -1;
|
injectorIndex = -1;
|
||||||
} else {
|
} else {
|
||||||
previousTView = lView[TVIEW];
|
previousTView = lView[TVIEW];
|
||||||
injectorIndex = getParentInjectorIndex(parentLocation);
|
injectorIndex = getParentInjectorIndex(parentLocation);
|
||||||
lView = getParentInjectorView(parentLocation, lView);
|
lView = getParentInjectorView(parentLocation, lView);
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Traverse up the injector tree until we find a potential match or until we know there
|
|
||||||
// *isn't* a match.
|
|
||||||
while (injectorIndex !== -1) {
|
|
||||||
parentLocation = lView[injectorIndex + PARENT_INJECTOR];
|
|
||||||
|
|
||||||
// Check the current injector. If it matches, see if it contains token.
|
|
||||||
const tView = lView[TVIEW];
|
|
||||||
if (bloomHasToken(bloomHash, injectorIndex, tView.data)) {
|
|
||||||
// At this point, we have an injector which *may* contain the token, so we step through
|
|
||||||
// the providers and directives associated with the injector's corresponding node to get
|
|
||||||
// the instance.
|
|
||||||
const instance: T|null = searchTokensOnInjector<T>(
|
|
||||||
injectorIndex, lView, token, previousTView, flags, hostTElementNode);
|
|
||||||
if (instance !== NOT_FOUND) {
|
|
||||||
return instance;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (shouldSearchParent(
|
|
||||||
flags, lView[TVIEW].data[injectorIndex + TNODE] === hostTElementNode) &&
|
// Traverse up the injector tree until we find a potential match or until we know there
|
||||||
bloomHasToken(bloomHash, injectorIndex, lView)) {
|
// *isn't* a match.
|
||||||
// The def wasn't found anywhere on this node, so it was a false positive.
|
while (injectorIndex !== -1) {
|
||||||
// Traverse up the tree and continue searching.
|
parentLocation = lView[injectorIndex + PARENT_INJECTOR];
|
||||||
previousTView = tView;
|
|
||||||
injectorIndex = getParentInjectorIndex(parentLocation);
|
// Check the current injector. If it matches, see if it contains token.
|
||||||
lView = getParentInjectorView(parentLocation, lView);
|
const tView = lView[TVIEW];
|
||||||
} else {
|
if (bloomHasToken(bloomHash, injectorIndex, tView.data)) {
|
||||||
// If we should not search parent OR If the ancestor bloom filter value does not have the
|
// At this point, we have an injector which *may* contain the token, so we step through
|
||||||
// bit corresponding to the directive we can give up on traversing up to find the specific
|
// the providers and directives associated with the injector's corresponding node to get
|
||||||
// injector.
|
// the instance.
|
||||||
injectorIndex = -1;
|
const instance: T|null = searchTokensOnInjector<T>(
|
||||||
|
injectorIndex, lView, token, previousTView, flags, hostTElementNode);
|
||||||
|
if (instance !== NOT_FOUND) {
|
||||||
|
return instance;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (shouldSearchParent(
|
||||||
|
flags, lView[TVIEW].data[injectorIndex + TNODE] === hostTElementNode) &&
|
||||||
|
bloomHasToken(bloomHash, injectorIndex, lView)) {
|
||||||
|
// The def wasn't found anywhere on this node, so it was a false positive.
|
||||||
|
// Traverse up the tree and continue searching.
|
||||||
|
previousTView = tView;
|
||||||
|
injectorIndex = getParentInjectorIndex(parentLocation);
|
||||||
|
lView = getParentInjectorView(parentLocation, lView);
|
||||||
|
} else {
|
||||||
|
// If we should not search parent OR If the ancestor bloom filter value does not have the
|
||||||
|
// bit corresponding to the directive we can give up on traversing up to find the specific
|
||||||
|
// injector.
|
||||||
|
injectorIndex = -1;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -570,7 +574,8 @@ export function injectInjector() {
|
||||||
|
|
||||||
export class NodeInjector implements Injector {
|
export class NodeInjector implements Injector {
|
||||||
constructor(
|
constructor(
|
||||||
private _tNode: TElementNode|TContainerNode|TElementContainerNode, private _lView: LView) {}
|
private _tNode: TElementNode|TContainerNode|TElementContainerNode|null,
|
||||||
|
private _lView: LView) {}
|
||||||
|
|
||||||
get(token: any, notFoundValue?: any): any {
|
get(token: any, notFoundValue?: any): any {
|
||||||
return getOrCreateInjectable(this._tNode, this._lView, token, undefined, notFoundValue);
|
return getOrCreateInjectable(this._tNode, this._lView, token, undefined, notFoundValue);
|
||||||
|
|
|
@ -190,7 +190,7 @@ export function createContainerRef(
|
||||||
const parentTNode = getParentInjectorTNode(parentLocation, this._hostView, this._hostTNode);
|
const parentTNode = getParentInjectorTNode(parentLocation, this._hostView, this._hostTNode);
|
||||||
|
|
||||||
return !hasParentInjector(parentLocation) || parentTNode == null ?
|
return !hasParentInjector(parentLocation) || parentTNode == null ?
|
||||||
new NullInjector() :
|
new NodeInjector(null, this._hostView) :
|
||||||
new NodeInjector(parentTNode, parentView);
|
new NodeInjector(parentTNode, parentView);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -152,9 +152,6 @@
|
||||||
{
|
{
|
||||||
"name": "NodeInjectorFactory"
|
"name": "NodeInjectorFactory"
|
||||||
},
|
},
|
||||||
{
|
|
||||||
"name": "NullInjector"
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
"name": "ObjectUnsubscribedErrorImpl"
|
"name": "ObjectUnsubscribedErrorImpl"
|
||||||
},
|
},
|
||||||
|
@ -269,9 +266,6 @@
|
||||||
{
|
{
|
||||||
"name": "_DuplicateMap"
|
"name": "_DuplicateMap"
|
||||||
},
|
},
|
||||||
{
|
|
||||||
"name": "_THROW_IF_NOT_FOUND"
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
"name": "__extends"
|
"name": "__extends"
|
||||||
},
|
},
|
||||||
|
|
|
@ -758,25 +758,23 @@ class TestComp {
|
||||||
.toThrowError('NodeInjector: NOT_FOUND [SimpleDirective]');
|
.toThrowError('NodeInjector: NOT_FOUND [SimpleDirective]');
|
||||||
});
|
});
|
||||||
|
|
||||||
fixmeIvy('FW-638: Exception thrown when getting VCRef.parentInjector on the root view')
|
it('should allow to use the NgModule injector from a root ViewContainerRef.parentInjector',
|
||||||
.it('should allow to use the NgModule injector from a root ViewContainerRef.parentInjector',
|
() => {
|
||||||
() => {
|
@Component({template: ''})
|
||||||
@Component({template: ''})
|
class MyComp {
|
||||||
class MyComp {
|
constructor(public vc: ViewContainerRef) {}
|
||||||
constructor(public vc: ViewContainerRef) {}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
const compFixture =
|
const compFixture = TestBed
|
||||||
TestBed
|
.configureTestingModule({
|
||||||
.configureTestingModule({
|
declarations: [MyComp],
|
||||||
declarations: [MyComp],
|
providers: [{provide: 'someToken', useValue: 'someValue'}]
|
||||||
providers: [{provide: 'someToken', useValue: 'someValue'}]
|
})
|
||||||
})
|
.createComponent(MyComp);
|
||||||
.createComponent(MyComp);
|
|
||||||
|
|
||||||
expect(compFixture.componentInstance.vc.parentInjector.get('someToken'))
|
expect(compFixture.componentInstance.vc.parentInjector.get('someToken'))
|
||||||
.toBe('someValue');
|
.toBe('someValue');
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
describe('static attributes', () => {
|
describe('static attributes', () => {
|
||||||
|
@ -901,31 +899,30 @@ class TestComp {
|
||||||
.toBe(el.children[0].nativeElement);
|
.toBe(el.children[0].nativeElement);
|
||||||
});
|
});
|
||||||
|
|
||||||
fixmeIvy('FW-638: Exception thrown when getting VCRef.parentInjector on the root view')
|
it('should inject ViewContainerRef', () => {
|
||||||
.it('should inject ViewContainerRef', () => {
|
@Component({template: ''})
|
||||||
@Component({template: ''})
|
class TestComp {
|
||||||
class TestComp {
|
constructor(public vcr: ViewContainerRef) {}
|
||||||
constructor(public vcr: ViewContainerRef) {}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
@NgModule({
|
@NgModule({
|
||||||
declarations: [TestComp],
|
declarations: [TestComp],
|
||||||
entryComponents: [TestComp],
|
entryComponents: [TestComp],
|
||||||
})
|
})
|
||||||
class TestModule {
|
class TestModule {
|
||||||
}
|
}
|
||||||
|
|
||||||
const testInjector = <Injector>{
|
const testInjector = <Injector>{
|
||||||
get: (token: any, notFoundValue: any) =>
|
get: (token: any, notFoundValue: any) =>
|
||||||
token === 'someToken' ? 'someNewValue' : notFoundValue
|
token === 'someToken' ? 'someNewValue' : notFoundValue
|
||||||
};
|
};
|
||||||
|
|
||||||
const compFactory = TestBed.configureTestingModule({imports: [TestModule]})
|
const compFactory = TestBed.configureTestingModule({imports: [TestModule]})
|
||||||
.get(ComponentFactoryResolver)
|
.get(ComponentFactoryResolver)
|
||||||
.resolveComponentFactory(TestComp);
|
.resolveComponentFactory(TestComp);
|
||||||
const component = compFactory.create(testInjector);
|
const component = compFactory.create(testInjector);
|
||||||
expect(component.instance.vcr.parentInjector.get('someToken')).toBe('someNewValue');
|
expect(component.instance.vcr.parentInjector.get('someToken')).toBe('someNewValue');
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should inject TemplateRef', () => {
|
it('should inject TemplateRef', () => {
|
||||||
TestBed.configureTestingModule({declarations: [NeedsViewContainerRef, NeedsTemplateRef]});
|
TestBed.configureTestingModule({declarations: [NeedsViewContainerRef, NeedsTemplateRef]});
|
||||||
|
|
|
@ -1926,6 +1926,8 @@ describe('ViewContainerRef', () => {
|
||||||
}
|
}
|
||||||
|
|
||||||
clear() { this._vcRef.clear(); }
|
clear() { this._vcRef.clear(); }
|
||||||
|
|
||||||
|
getVCRefParentInjector() { return this._vcRef.parentInjector; }
|
||||||
}
|
}
|
||||||
|
|
||||||
// https://stackblitz.com/edit/angular-xxpffd?file=src%2Findex.html
|
// https://stackblitz.com/edit/angular-xxpffd?file=src%2Findex.html
|
||||||
|
@ -1952,6 +1954,21 @@ describe('ViewContainerRef', () => {
|
||||||
expect(fixture.outerHtml).toBe('<div host="mark"></div>');
|
expect(fixture.outerHtml).toBe('<div host="mark"></div>');
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it('should allow getting the parentInjector of the VCRef which was injected into the root (bootstrapped) component',
|
||||||
|
() => {
|
||||||
|
const fixture = new ComponentFixture(AppCmpt, {
|
||||||
|
injector: {
|
||||||
|
get: (token: any) => {
|
||||||
|
if (token === 'foo') return 'bar';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
expect(fixture.outerHtml).toBe('<div host="mark"></div>');
|
||||||
|
|
||||||
|
const parentInjector = fixture.component.getVCRefParentInjector();
|
||||||
|
expect(parentInjector.get('foo')).toEqual('bar');
|
||||||
|
});
|
||||||
|
|
||||||
it('should check bindings for components dynamically created by root component', () => {
|
it('should check bindings for components dynamically created by root component', () => {
|
||||||
class DynamicCompWithBindings {
|
class DynamicCompWithBindings {
|
||||||
checkCount = 0;
|
checkCount = 0;
|
||||||
|
|
Loading…
Reference in New Issue