fix(ivy): update ViewContainerRef to get the correct parentInjector (#35013)
PR Close #35013
This commit is contained in:
parent
519e9e1ae8
commit
b8ffcf973c
@ -12,8 +12,10 @@ import {DECLARATION_VIEW, LView, T_HOST} from './interfaces/view';
|
|||||||
import {getParentInjectorViewOffset} from './util/injector_utils';
|
import {getParentInjectorViewOffset} from './util/injector_utils';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Unwraps a parent injector location number to find the view offset from the current injector,
|
* If `startTNode.parent` exists and has an injector, returns TNode for that injector.
|
||||||
* then walks up the declaration view tree until the TNode of the parent injector is found.
|
* Otherwise, unwraps a parent injector location number to find the view offset from the current
|
||||||
|
* injector, then walks up the declaration view tree until the TNode of the parent injector is
|
||||||
|
* found.
|
||||||
*
|
*
|
||||||
* @param location The location of the parent injector, which contains the view offset
|
* @param location The location of the parent injector, which contains the view offset
|
||||||
* @param startView The LView instance from which to start walking up the view tree
|
* @param startView The LView instance from which to start walking up the view tree
|
||||||
@ -23,14 +25,17 @@ import {getParentInjectorViewOffset} from './util/injector_utils';
|
|||||||
export function getParentInjectorTNode(
|
export function getParentInjectorTNode(
|
||||||
location: RelativeInjectorLocation, startView: LView, startTNode: TNode): TElementNode|
|
location: RelativeInjectorLocation, startView: LView, startTNode: TNode): TElementNode|
|
||||||
TContainerNode|null {
|
TContainerNode|null {
|
||||||
|
// If there is an injector on the parent TNode, retrieve the TNode for that injector.
|
||||||
if (startTNode.parent && startTNode.parent.injectorIndex !== -1) {
|
if (startTNode.parent && startTNode.parent.injectorIndex !== -1) {
|
||||||
// view offset is 0
|
// view offset is 0
|
||||||
const injectorIndex = startTNode.parent.injectorIndex;
|
const injectorIndex = startTNode.parent.injectorIndex;
|
||||||
let parentTNode = startTNode.parent;
|
let tNode = startTNode.parent;
|
||||||
while (parentTNode.parent != null && injectorIndex == parentTNode.injectorIndex) {
|
// If tNode.injectorIndex === tNode.parent.injectorIndex, then the index belongs to a parent
|
||||||
parentTNode = parentTNode.parent;
|
// injector.
|
||||||
|
while (tNode.parent != null && injectorIndex == tNode.parent.injectorIndex) {
|
||||||
|
tNode = tNode.parent;
|
||||||
}
|
}
|
||||||
return parentTNode;
|
return tNode;
|
||||||
}
|
}
|
||||||
let viewOffset = getParentInjectorViewOffset(location);
|
let viewOffset = getParentInjectorViewOffset(location);
|
||||||
// view offset is 1
|
// view offset is 1
|
||||||
|
@ -470,6 +470,64 @@ describe('di', () => {
|
|||||||
fixture.detectChanges();
|
fixture.detectChanges();
|
||||||
expect(divElement.id).toBe('bar');
|
expect(divElement.id).toBe('bar');
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it('dynamic components should find dependencies when parent is projected', () => {
|
||||||
|
@Directive({selector: '[dirA]'})
|
||||||
|
class DirA {
|
||||||
|
}
|
||||||
|
@Directive({selector: '[dirB]'})
|
||||||
|
class DirB {
|
||||||
|
}
|
||||||
|
@Component({selector: 'child', template: ''})
|
||||||
|
class Child {
|
||||||
|
constructor(@Optional() readonly dirA: DirA, @Optional() readonly dirB: DirB) {}
|
||||||
|
}
|
||||||
|
@Component({
|
||||||
|
selector: 'projector',
|
||||||
|
template: '<ng-content></ng-content>',
|
||||||
|
})
|
||||||
|
class Projector {
|
||||||
|
}
|
||||||
|
|
||||||
|
@Component({
|
||||||
|
template: `
|
||||||
|
<projector>
|
||||||
|
<div dirA>
|
||||||
|
<ng-container #childOrigin></ng-container>
|
||||||
|
<ng-container #childOriginWithDirB dirB></ng-container>
|
||||||
|
</div>
|
||||||
|
</projector>`,
|
||||||
|
entryComponents: [Child]
|
||||||
|
})
|
||||||
|
class MyApp {
|
||||||
|
@ViewChild('childOrigin', {read: ViewContainerRef, static: true})
|
||||||
|
childOrigin !: ViewContainerRef;
|
||||||
|
@ViewChild('childOriginWithDirB', {read: ViewContainerRef, static: true})
|
||||||
|
childOriginWithDirB !: ViewContainerRef;
|
||||||
|
childFactory = this.resolver.resolveComponentFactory(Child);
|
||||||
|
|
||||||
|
constructor(readonly resolver: ComponentFactoryResolver, readonly injector: Injector) {}
|
||||||
|
|
||||||
|
addChild() { return this.childOrigin.createComponent(this.childFactory); }
|
||||||
|
addChildWithDirB() { return this.childOriginWithDirB.createComponent(this.childFactory); }
|
||||||
|
}
|
||||||
|
|
||||||
|
const fixture =
|
||||||
|
TestBed.configureTestingModule({declarations: [Child, DirA, DirB, Projector, MyApp]})
|
||||||
|
.createComponent(MyApp);
|
||||||
|
const child = fixture.componentInstance.addChild();
|
||||||
|
expect(child).toBeDefined();
|
||||||
|
expect(child.instance.dirA)
|
||||||
|
.not.toBeNull('dirA should be found. It is on the parent of the viewContainerRef.');
|
||||||
|
const child2 = fixture.componentInstance.addChildWithDirB();
|
||||||
|
expect(child2).toBeDefined();
|
||||||
|
expect(child2.instance.dirA)
|
||||||
|
.not.toBeNull('dirA should be found. It is on the parent of the viewContainerRef.');
|
||||||
|
expect(child2.instance.dirB)
|
||||||
|
.toBeNull(
|
||||||
|
'dirB appears on the ng-container and should not be found because the ' +
|
||||||
|
'viewContainerRef.createComponent node is inserted next to the container.');
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should throw if directive is not found anywhere', () => {
|
it('should throw if directive is not found anywhere', () => {
|
||||||
|
Loading…
x
Reference in New Issue
Block a user