fix(ivy): should not throw when getting VCRef.parentInjector on the root view (#27909)

PR Close #27909
This commit is contained in:
Marc Laval 2019-01-03 14:43:06 +01:00 committed by Kara Erickson
parent fb7816fed4
commit 929334b0bf
5 changed files with 129 additions and 116 deletions

View File

@ -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,9 +292,10 @@ 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 {
if (tNode) {
const bloomHash = bloomHashBitOrFactory(token); const bloomHash = bloomHashBitOrFactory(token);
// If the ID stored here is a function, this is a special object like ElementRef or TemplateRef // If the ID stored here is a function, this is a special object like ElementRef or TemplateRef
// so just call the factory function to create it. // so just call the factory function to create it.
@ -315,15 +316,17 @@ export function getOrCreateInjectable<T>(
} else if (typeof bloomHash == 'number') { } else if (typeof bloomHash == 'number') {
// If the token has a bloom hash, then it is a token which could be in NodeInjector. // 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
// injector.
let previousTView: TView|null = null; let previousTView: TView|null = null;
let injectorIndex = getInjectorIndex(tNode, lView); let injectorIndex = getInjectorIndex(tNode, lView);
let parentLocation: RelativeInjectorLocation = NO_PARENT_INJECTOR; let parentLocation: RelativeInjectorLocation = NO_PARENT_INJECTOR;
let hostTElementNode: TNode|null = let hostTElementNode: TNode|null =
flags & InjectFlags.Host ? findComponentView(lView)[HOST_NODE] : 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
// searching
// the parent injector. // the parent injector.
if (injectorIndex === -1 || flags & InjectFlags.SkipSelf) { if (injectorIndex === -1 || flags & InjectFlags.SkipSelf) {
parentLocation = injectorIndex === -1 ? getParentInjectorLocation(tNode, lView) : parentLocation = injectorIndex === -1 ? getParentInjectorLocation(tNode, lView) :
@ -371,6 +374,7 @@ export function getOrCreateInjectable<T>(
} }
} }
} }
}
if (flags & InjectFlags.Optional && notFoundValue === undefined) { if (flags & InjectFlags.Optional && notFoundValue === undefined) {
// This must be set or the NullInjector will throw for optional deps // This must be set or the NullInjector will throw for optional deps
@ -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);

View File

@ -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);
} }

View File

@ -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"
}, },

View File

@ -758,16 +758,14 @@ 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'}]
@ -901,8 +899,7 @@ 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) {}

View File

@ -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;