fix(core): allow to query content of templates that are stamped out at a different place
Previously, if a `TemplateRef` was created in a `ViewContainerRef` at a different place, the content was not query able at all. With this change, the content of the template can be queried as if it was stamped out at the declaration place of the template. E.g. in the following example, the `QueryList<ChildCmp>` will be filled once the button is clicked. ``` @Component({ selector: ‘my-comp’, template: ‘<button #vc (click)=“createView()”></button>’ }) class MyComp { @ContentChildren(ChildCmp) children: QueryList<ChildCmp>; @ContentChildren(TemplateRef) template: TemplateRef; @ViewChild(‘vc’, {read: ViewContainerRef}) vc: ViewContainerRef; createView() { this.vc.createEmbeddedView(this.template); } } @Component({ template: ` <my-comp> <template><child-cmp></child-cmp></template> </my-comp> ` }) class App {} ``` Closes #12283 Closes #12094
This commit is contained in:
parent
80d36b8db4
commit
f2bbef3e33
|
@ -73,7 +73,7 @@ export class CompileElement extends CompileNode {
|
||||||
o.THIS_EXPR.callMethod('injector', [o.literal(this.nodeIndex)]));
|
o.THIS_EXPR.callMethod('injector', [o.literal(this.nodeIndex)]));
|
||||||
this.instances.set(
|
this.instances.set(
|
||||||
resolveIdentifierToken(Identifiers.Renderer).reference, o.THIS_EXPR.prop('renderer'));
|
resolveIdentifierToken(Identifiers.Renderer).reference, o.THIS_EXPR.prop('renderer'));
|
||||||
if (this.hasViewContainer) {
|
if (this.hasViewContainer || this.hasEmbeddedView) {
|
||||||
this._createViewContainer();
|
this._createViewContainer();
|
||||||
}
|
}
|
||||||
if (this.component) {
|
if (this.component) {
|
||||||
|
|
|
@ -459,6 +459,11 @@ function createViewClass(
|
||||||
if (view.genConfig.genDebugInfo) {
|
if (view.genConfig.genDebugInfo) {
|
||||||
superConstructorArgs.push(nodeDebugInfosVar);
|
superConstructorArgs.push(nodeDebugInfosVar);
|
||||||
}
|
}
|
||||||
|
if (view.viewType === ViewType.EMBEDDED) {
|
||||||
|
viewConstructorArgs.push(new o.FnParam(
|
||||||
|
'declaredViewContainer', o.importType(resolveIdentifier(Identifiers.ViewContainer))));
|
||||||
|
superConstructorArgs.push(o.variable('declaredViewContainer'));
|
||||||
|
}
|
||||||
var viewMethods = [
|
var viewMethods = [
|
||||||
new o.ClassMethod(
|
new o.ClassMethod(
|
||||||
'createInternal', [new o.FnParam(rootSelectorVar.name, o.STRING_TYPE)],
|
'createInternal', [new o.FnParam(rootSelectorVar.name, o.STRING_TYPE)],
|
||||||
|
@ -676,7 +681,7 @@ function generateVisitNodesStmts(
|
||||||
return stmts;
|
return stmts;
|
||||||
}
|
}
|
||||||
|
|
||||||
function generateCreateEmbeddedViewsMethod(view: CompileView) {
|
function generateCreateEmbeddedViewsMethod(view: CompileView): o.ClassMethod {
|
||||||
const nodeIndexVar = o.variable('nodeIndex');
|
const nodeIndexVar = o.variable('nodeIndex');
|
||||||
const stmts: o.Statement[] = [];
|
const stmts: o.Statement[] = [];
|
||||||
view.nodes.forEach((node) => {
|
view.nodes.forEach((node) => {
|
||||||
|
@ -686,12 +691,15 @@ function generateCreateEmbeddedViewsMethod(view: CompileView) {
|
||||||
stmts.push(new o.IfStmt(
|
stmts.push(new o.IfStmt(
|
||||||
nodeIndexVar.equals(o.literal(node.nodeIndex)),
|
nodeIndexVar.equals(o.literal(node.nodeIndex)),
|
||||||
[new o.ReturnStatement(node.embeddedView.classExpr.instantiate([
|
[new o.ReturnStatement(node.embeddedView.classExpr.instantiate([
|
||||||
ViewProperties.viewUtils, o.THIS_EXPR, o.literal(node.nodeIndex), node.renderNode
|
ViewProperties.viewUtils, o.THIS_EXPR, o.literal(node.nodeIndex), node.renderNode,
|
||||||
|
node.viewContainer
|
||||||
]))]));
|
]))]));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
stmts.push(new o.ReturnStatement(o.NULL_EXPR));
|
if (stmts.length > 0) {
|
||||||
|
stmts.push(new o.ReturnStatement(o.NULL_EXPR));
|
||||||
|
}
|
||||||
return new o.ClassMethod(
|
return new o.ClassMethod(
|
||||||
'createEmbeddedViewInternal', [new o.FnParam(nodeIndexVar.name, o.NUMBER_TYPE)], stmts,
|
'createEmbeddedViewInternal', [new o.FnParam(nodeIndexVar.name, o.NUMBER_TYPE)], stmts,
|
||||||
o.importType(resolveIdentifier(Identifiers.AppView), [o.DYNAMIC_TYPE]));
|
o.importType(resolveIdentifier(Identifiers.AppView), [o.DYNAMIC_TYPE]));
|
||||||
|
|
|
@ -41,7 +41,7 @@ export abstract class AppView<T> {
|
||||||
lastRootNode: any;
|
lastRootNode: any;
|
||||||
allNodes: any[];
|
allNodes: any[];
|
||||||
disposables: Function[];
|
disposables: Function[];
|
||||||
viewContainerElement: ViewContainer = null;
|
viewContainer: ViewContainer = null;
|
||||||
|
|
||||||
numberOfChecks: number = 0;
|
numberOfChecks: number = 0;
|
||||||
|
|
||||||
|
@ -58,7 +58,8 @@ export abstract class AppView<T> {
|
||||||
constructor(
|
constructor(
|
||||||
public clazz: any, public componentType: RenderComponentType, public type: ViewType,
|
public clazz: any, public componentType: RenderComponentType, public type: ViewType,
|
||||||
public viewUtils: ViewUtils, public parentView: AppView<any>, public parentIndex: number,
|
public viewUtils: ViewUtils, public parentView: AppView<any>, public parentIndex: number,
|
||||||
public parentElement: any, public cdMode: ChangeDetectorStatus) {
|
public parentElement: any, public cdMode: ChangeDetectorStatus,
|
||||||
|
public declaredViewContainer: ViewContainer = null) {
|
||||||
this.ref = new ViewRef_(this);
|
this.ref = new ViewRef_(this);
|
||||||
if (type === ViewType.COMPONENT || type === ViewType.HOST) {
|
if (type === ViewType.COMPONENT || type === ViewType.HOST) {
|
||||||
this.renderer = viewUtils.renderComponent(componentType);
|
this.renderer = viewUtils.renderComponent(componentType);
|
||||||
|
@ -139,8 +140,8 @@ export abstract class AppView<T> {
|
||||||
detachAndDestroy() {
|
detachAndDestroy() {
|
||||||
if (this._hasExternalHostElement) {
|
if (this._hasExternalHostElement) {
|
||||||
this.detach();
|
this.detach();
|
||||||
} else if (isPresent(this.viewContainerElement)) {
|
} else if (isPresent(this.viewContainer)) {
|
||||||
this.viewContainerElement.detachView(this.viewContainerElement.nestedViews.indexOf(this));
|
this.viewContainer.detachView(this.viewContainer.nestedViews.indexOf(this));
|
||||||
}
|
}
|
||||||
this.destroy();
|
this.destroy();
|
||||||
}
|
}
|
||||||
|
@ -185,6 +186,18 @@ export abstract class AppView<T> {
|
||||||
} else {
|
} else {
|
||||||
this._renderDetach();
|
this._renderDetach();
|
||||||
}
|
}
|
||||||
|
if (this.declaredViewContainer && this.declaredViewContainer !== this.viewContainer) {
|
||||||
|
const projectedViews = this.declaredViewContainer.projectedViews;
|
||||||
|
const index = projectedViews.indexOf(this);
|
||||||
|
// perf: pop is faster than splice!
|
||||||
|
if (index >= projectedViews.length - 1) {
|
||||||
|
projectedViews.pop();
|
||||||
|
} else {
|
||||||
|
projectedViews.splice(index, 1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
this.viewContainer = null;
|
||||||
|
this.dirtyParentQueriesInternal();
|
||||||
}
|
}
|
||||||
|
|
||||||
private _renderDetach() {
|
private _renderDetach() {
|
||||||
|
@ -195,7 +208,25 @@ export abstract class AppView<T> {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
attachAfter(prevNode: any) {
|
attachAfter(viewContainer: ViewContainer, prevView: AppView<any>) {
|
||||||
|
this._renderAttach(viewContainer, prevView);
|
||||||
|
this.viewContainer = viewContainer;
|
||||||
|
if (this.declaredViewContainer && this.declaredViewContainer !== viewContainer) {
|
||||||
|
if (!this.declaredViewContainer.projectedViews) {
|
||||||
|
this.declaredViewContainer.projectedViews = [];
|
||||||
|
}
|
||||||
|
this.declaredViewContainer.projectedViews.push(this);
|
||||||
|
}
|
||||||
|
this.dirtyParentQueriesInternal();
|
||||||
|
}
|
||||||
|
|
||||||
|
moveAfter(viewContainer: ViewContainer, prevView: AppView<any>) {
|
||||||
|
this._renderAttach(viewContainer, prevView);
|
||||||
|
this.dirtyParentQueriesInternal();
|
||||||
|
}
|
||||||
|
|
||||||
|
private _renderAttach(viewContainer: ViewContainer, prevView: AppView<any>) {
|
||||||
|
const prevNode = prevView ? prevView.lastRootNode : viewContainer.nativeElement;
|
||||||
if (this._directRenderer) {
|
if (this._directRenderer) {
|
||||||
const nextSibling = this._directRenderer.nextSibling(prevNode);
|
const nextSibling = this._directRenderer.nextSibling(prevNode);
|
||||||
if (nextSibling) {
|
if (nextSibling) {
|
||||||
|
@ -282,18 +313,6 @@ export abstract class AppView<T> {
|
||||||
*/
|
*/
|
||||||
detectChangesInternal(throwOnChange: boolean): void {}
|
detectChangesInternal(throwOnChange: boolean): void {}
|
||||||
|
|
||||||
markContentChildAsMoved(viewContainer: ViewContainer): void { this.dirtyParentQueriesInternal(); }
|
|
||||||
|
|
||||||
addToContentChildren(viewContainer: ViewContainer): void {
|
|
||||||
this.viewContainerElement = viewContainer;
|
|
||||||
this.dirtyParentQueriesInternal();
|
|
||||||
}
|
|
||||||
|
|
||||||
removeFromContentChildren(viewContainer: ViewContainer): void {
|
|
||||||
this.dirtyParentQueriesInternal();
|
|
||||||
this.viewContainerElement = null;
|
|
||||||
}
|
|
||||||
|
|
||||||
markAsCheckOnce(): void { this.cdMode = ChangeDetectorStatus.CheckOnce; }
|
markAsCheckOnce(): void { this.cdMode = ChangeDetectorStatus.CheckOnce; }
|
||||||
|
|
||||||
markPathToRootAsCheckOnce(): void {
|
markPathToRootAsCheckOnce(): void {
|
||||||
|
@ -305,7 +324,7 @@ export abstract class AppView<T> {
|
||||||
if (c.type === ViewType.COMPONENT) {
|
if (c.type === ViewType.COMPONENT) {
|
||||||
c = c.parentView;
|
c = c.parentView;
|
||||||
} else {
|
} else {
|
||||||
c = c.viewContainerElement ? c.viewContainerElement.parentView : null;
|
c = c.viewContainer ? c.viewContainer.parentView : null;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -323,8 +342,11 @@ export class DebugAppView<T> extends AppView<T> {
|
||||||
constructor(
|
constructor(
|
||||||
clazz: any, componentType: RenderComponentType, type: ViewType, viewUtils: ViewUtils,
|
clazz: any, componentType: RenderComponentType, type: ViewType, viewUtils: ViewUtils,
|
||||||
parentView: AppView<any>, parentIndex: number, parentNode: any, cdMode: ChangeDetectorStatus,
|
parentView: AppView<any>, parentIndex: number, parentNode: any, cdMode: ChangeDetectorStatus,
|
||||||
public staticNodeDebugInfos: StaticNodeDebugInfo[]) {
|
public staticNodeDebugInfos: StaticNodeDebugInfo[],
|
||||||
super(clazz, componentType, type, viewUtils, parentView, parentIndex, parentNode, cdMode);
|
declaredViewContainer: ViewContainer = null) {
|
||||||
|
super(
|
||||||
|
clazz, componentType, type, viewUtils, parentView, parentIndex, parentNode, cdMode,
|
||||||
|
declaredViewContainer);
|
||||||
}
|
}
|
||||||
|
|
||||||
create(context: T) {
|
create(context: T) {
|
||||||
|
|
|
@ -22,6 +22,9 @@ import {ViewType} from './view_type';
|
||||||
*/
|
*/
|
||||||
export class ViewContainer {
|
export class ViewContainer {
|
||||||
public nestedViews: AppView<any>[];
|
public nestedViews: AppView<any>[];
|
||||||
|
// views that have been declared at the place of this view container,
|
||||||
|
// but inserted into another view container
|
||||||
|
public projectedViews: AppView<any>[];
|
||||||
|
|
||||||
constructor(
|
constructor(
|
||||||
public index: number, public parentIndex: number, public parentView: AppView<any>,
|
public index: number, public parentIndex: number, public parentView: AppView<any>,
|
||||||
|
@ -59,13 +62,22 @@ export class ViewContainer {
|
||||||
}
|
}
|
||||||
|
|
||||||
mapNestedViews(nestedViewClass: any, callback: Function): any[] {
|
mapNestedViews(nestedViewClass: any, callback: Function): any[] {
|
||||||
var result: any[] /** TODO #9100 */ = [];
|
var result: any[] = [];
|
||||||
if (isPresent(this.nestedViews)) {
|
if (this.nestedViews) {
|
||||||
this.nestedViews.forEach((nestedView) => {
|
for (var i = 0; i < this.nestedViews.length; i++) {
|
||||||
|
const nestedView = this.nestedViews[i];
|
||||||
if (nestedView.clazz === nestedViewClass) {
|
if (nestedView.clazz === nestedViewClass) {
|
||||||
result.push(callback(nestedView));
|
result.push(callback(nestedView));
|
||||||
}
|
}
|
||||||
});
|
}
|
||||||
|
}
|
||||||
|
if (this.projectedViews) {
|
||||||
|
for (var i = 0; i < this.projectedViews.length; i++) {
|
||||||
|
const projectedView = this.projectedViews[i];
|
||||||
|
if (projectedView.clazz === nestedViewClass) {
|
||||||
|
result.push(callback(projectedView));
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
@ -82,17 +94,8 @@ export class ViewContainer {
|
||||||
}
|
}
|
||||||
nestedViews.splice(previousIndex, 1);
|
nestedViews.splice(previousIndex, 1);
|
||||||
nestedViews.splice(currentIndex, 0, view);
|
nestedViews.splice(currentIndex, 0, view);
|
||||||
var refRenderNode: any /** TODO #9100 */;
|
const prevView = currentIndex > 0 ? nestedViews[currentIndex - 1] : null;
|
||||||
if (currentIndex > 0) {
|
view.moveAfter(this, prevView);
|
||||||
var prevView = nestedViews[currentIndex - 1];
|
|
||||||
refRenderNode = prevView.lastRootNode;
|
|
||||||
} else {
|
|
||||||
refRenderNode = this.nativeElement;
|
|
||||||
}
|
|
||||||
if (isPresent(refRenderNode)) {
|
|
||||||
view.attachAfter(refRenderNode);
|
|
||||||
}
|
|
||||||
view.markContentChildAsMoved(this);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
attachView(view: AppView<any>, viewIndex: number) {
|
attachView(view: AppView<any>, viewIndex: number) {
|
||||||
|
@ -110,17 +113,8 @@ export class ViewContainer {
|
||||||
} else {
|
} else {
|
||||||
nestedViews.splice(viewIndex, 0, view);
|
nestedViews.splice(viewIndex, 0, view);
|
||||||
}
|
}
|
||||||
var refRenderNode: any /** TODO #9100 */;
|
const prevView = viewIndex > 0 ? nestedViews[viewIndex - 1] : null;
|
||||||
if (viewIndex > 0) {
|
view.attachAfter(this, prevView);
|
||||||
var prevView = nestedViews[viewIndex - 1];
|
|
||||||
refRenderNode = prevView.lastRootNode;
|
|
||||||
} else {
|
|
||||||
refRenderNode = this.nativeElement;
|
|
||||||
}
|
|
||||||
if (isPresent(refRenderNode)) {
|
|
||||||
view.attachAfter(refRenderNode);
|
|
||||||
}
|
|
||||||
view.addToContentChildren(this);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
detachView(viewIndex: number): AppView<any> {
|
detachView(viewIndex: number): AppView<any> {
|
||||||
|
@ -135,8 +129,6 @@ export class ViewContainer {
|
||||||
throw new Error(`Component views can't be moved!`);
|
throw new Error(`Component views can't be moved!`);
|
||||||
}
|
}
|
||||||
view.detach();
|
view.detach();
|
||||||
|
|
||||||
view.removeFromContentChildren(this);
|
|
||||||
return view;
|
return view;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -43,7 +43,8 @@ export function main() {
|
||||||
NeedsContentChildWithRead,
|
NeedsContentChildWithRead,
|
||||||
NeedsViewChildrenWithRead,
|
NeedsViewChildrenWithRead,
|
||||||
NeedsViewChildWithRead,
|
NeedsViewChildWithRead,
|
||||||
NeedsViewContainerWithRead
|
NeedsViewContainerWithRead,
|
||||||
|
ManualProjecting
|
||||||
]
|
]
|
||||||
}));
|
}));
|
||||||
|
|
||||||
|
@ -505,6 +506,25 @@ export function main() {
|
||||||
expect(q.query4).toBeDefined();
|
expect(q.query4).toBeDefined();
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
describe('query over moved templates', () => {
|
||||||
|
it('should include manually projected templates in queries', () => {
|
||||||
|
const template =
|
||||||
|
'<manual-projecting #q><template><div text="1"></div></template></manual-projecting>';
|
||||||
|
const view = createTestCmpAndDetectChanges(MyComp0, template);
|
||||||
|
const q = view.debugElement.children[0].references['q'];
|
||||||
|
expect(q.query.length).toBe(0);
|
||||||
|
|
||||||
|
q.create();
|
||||||
|
view.detectChanges();
|
||||||
|
expect(q.query.map((d: TextDirective) => d.text)).toEqual(['1']);
|
||||||
|
|
||||||
|
q.destroy();
|
||||||
|
view.detectChanges();
|
||||||
|
expect(q.query.length).toBe(0);
|
||||||
|
});
|
||||||
|
|
||||||
|
});
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -751,3 +771,18 @@ class MyComp0 {
|
||||||
@Component({selector: 'my-comp', template: ''})
|
@Component({selector: 'my-comp', template: ''})
|
||||||
class MyCompBroken0 {
|
class MyCompBroken0 {
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Component({selector: 'manual-projecting', template: '<div #vc></div>'})
|
||||||
|
class ManualProjecting {
|
||||||
|
@ContentChild(TemplateRef) template: TemplateRef<any>;
|
||||||
|
|
||||||
|
@ViewChild('vc', {read: ViewContainerRef})
|
||||||
|
vc: ViewContainerRef;
|
||||||
|
|
||||||
|
@ContentChildren(TextDirective)
|
||||||
|
query: QueryList<TextDirective>;
|
||||||
|
|
||||||
|
create() { this.vc.createEmbeddedView(this.template); }
|
||||||
|
|
||||||
|
destroy() { this.vc.clear(); }
|
||||||
|
}
|
||||||
|
|
Loading…
Reference in New Issue