feat(ivy): add support for local refs on ng-template (#25482)

PR Close #25482
This commit is contained in:
Pawel Kozlowski 2018-08-14 16:25:01 +02:00 committed by Jason Aden
parent 07d8d3994c
commit 31f0f5b3c3
10 changed files with 213 additions and 129 deletions

View File

@ -28,6 +28,7 @@ export {
injectAttribute as ɵinjectAttribute, injectAttribute as ɵinjectAttribute,
getFactoryOf as ɵgetFactoryOf, getFactoryOf as ɵgetFactoryOf,
getInheritedFactory as ɵgetInheritedFactory, getInheritedFactory as ɵgetInheritedFactory,
templateRefExtractor as ɵtemplateRefExtractor,
PublicFeature as ɵPublicFeature, PublicFeature as ɵPublicFeature,
InheritDefinitionFeature as ɵInheritDefinitionFeature, InheritDefinitionFeature as ɵInheritDefinitionFeature,
NgOnChangesFeature as ɵNgOnChangesFeature, NgOnChangesFeature as ɵNgOnChangesFeature,

View File

@ -27,7 +27,7 @@ import {addToViewTree, assertPreviousIsParent, createEmbeddedViewNode, createLCo
import {VIEWS} from './interfaces/container'; import {VIEWS} from './interfaces/container';
import {DirectiveDefInternal, RenderFlags} from './interfaces/definition'; import {DirectiveDefInternal, RenderFlags} from './interfaces/definition';
import {LInjector} from './interfaces/injector'; import {LInjector} from './interfaces/injector';
import {AttributeMarker, LContainerNode, LElementContainerNode, LElementNode, LNode, LViewNode, TContainerNode, TElementNode, TNodeFlags, TNodeType} from './interfaces/node'; import {AttributeMarker, LContainerNode, LElementContainerNode, LElementNode, LNode, LNodeWithLocalRefs, LViewNode, TContainerNode, TElementNode, TNodeFlags, TNodeType} from './interfaces/node';
import {LQueries, QueryReadType} from './interfaces/query'; import {LQueries, QueryReadType} from './interfaces/query';
import {Renderer3} from './interfaces/renderer'; import {Renderer3} from './interfaces/renderer';
import {DIRECTIVES, HOST_NODE, INJECTOR, LViewData, QUERIES, RENDERER, TVIEW, TView} from './interfaces/view'; import {DIRECTIVES, HOST_NODE, INJECTOR, LViewData, QUERIES, RENDERER, TVIEW, TView} from './interfaces/view';
@ -814,3 +814,11 @@ class TemplateRef<T> implements viewEngine_TemplateRef<T> {
return viewRef; return viewRef;
} }
} }
/**
* Retrieves `TemplateRef` instance from `Injector` when a local reference is placed on the
* `<ng-template>` element.
*/
export function templateRefExtractor(lNode: LNodeWithLocalRefs) {
return getOrCreateTemplateRef(getOrCreateNodeInjectorForNode(lNode));
}

View File

@ -14,7 +14,7 @@ import {PublicFeature} from './features/public_feature';
import {BaseDef, ComponentDef, ComponentDefInternal, ComponentTemplate, ComponentType, DirectiveDef, DirectiveDefFlags, DirectiveDefInternal, DirectiveType, PipeDef} from './interfaces/definition'; import {BaseDef, ComponentDef, ComponentDefInternal, ComponentTemplate, ComponentType, DirectiveDef, DirectiveDefFlags, DirectiveDefInternal, DirectiveType, PipeDef} from './interfaces/definition';
export {ComponentFactory, ComponentFactoryResolver, ComponentRef, WRAP_RENDERER_FACTORY2} from './component_ref'; export {ComponentFactory, ComponentFactoryResolver, ComponentRef, WRAP_RENDERER_FACTORY2} from './component_ref';
export {QUERY_READ_CONTAINER_REF, QUERY_READ_ELEMENT_REF, QUERY_READ_FROM_NODE, QUERY_READ_TEMPLATE_REF, directiveInject, getFactoryOf, getInheritedFactory, injectAttribute, injectChangeDetectorRef, injectComponentFactoryResolver, injectElementRef, injectTemplateRef, injectViewContainerRef} from './di'; export {QUERY_READ_CONTAINER_REF, QUERY_READ_ELEMENT_REF, QUERY_READ_FROM_NODE, QUERY_READ_TEMPLATE_REF, directiveInject, getFactoryOf, getInheritedFactory, injectAttribute, injectChangeDetectorRef, injectComponentFactoryResolver, injectElementRef, injectTemplateRef, injectViewContainerRef, templateRefExtractor} from './di';
export {RenderFlags} from './interfaces/definition'; export {RenderFlags} from './interfaces/definition';
export {CssSelectorList} from './interfaces/projection'; export {CssSelectorList} from './interfaces/projection';

View File

@ -18,10 +18,10 @@ import {executeHooks, executeInitHooks, queueInitHooks, queueLifecycleHooks} fro
import {ACTIVE_INDEX, LContainer, RENDER_PARENT, VIEWS} from './interfaces/container'; import {ACTIVE_INDEX, LContainer, RENDER_PARENT, VIEWS} from './interfaces/container';
import {ComponentDefInternal, ComponentQuery, ComponentTemplate, DirectiveDefInternal, DirectiveDefListOrFactory, InitialStylingFlags, PipeDefListOrFactory, RenderFlags} from './interfaces/definition'; import {ComponentDefInternal, ComponentQuery, ComponentTemplate, DirectiveDefInternal, DirectiveDefListOrFactory, InitialStylingFlags, PipeDefListOrFactory, RenderFlags} from './interfaces/definition';
import {LInjector} from './interfaces/injector'; import {LInjector} from './interfaces/injector';
import {AttributeMarker, InitialInputData, InitialInputs, LContainerNode, LElementContainerNode, LElementNode, LNode, LProjectionNode, LTextNode, LViewNode, PropertyAliasValue, PropertyAliases, TAttributes, TContainerNode, TElementNode, TNode, TNodeFlags, TNodeType} from './interfaces/node'; import {AttributeMarker, InitialInputData, InitialInputs, LContainerNode, LElementContainerNode, LElementNode, LNode, LNodeWithLocalRefs, LProjectionNode, LTextNode, LViewNode, LocalRefExtractor, PropertyAliasValue, PropertyAliases, TAttributes, TContainerNode, TElementNode, TNode, TNodeFlags, TNodeType} from './interfaces/node';
import {CssSelectorList, NG_PROJECT_AS_ATTR_NAME} from './interfaces/projection'; import {CssSelectorList, NG_PROJECT_AS_ATTR_NAME} from './interfaces/projection';
import {LQueries} from './interfaces/query'; import {LQueries} from './interfaces/query';
import {ProceduralRenderer3, RComment, RElement, RText, Renderer3, RendererFactory3, RendererStyleFlags3, isProceduralRenderer} from './interfaces/renderer'; import {ProceduralRenderer3, RComment, RElement, RNode, RText, Renderer3, RendererFactory3, RendererStyleFlags3, isProceduralRenderer} from './interfaces/renderer';
import {BINDING_INDEX, CLEANUP, CONTENT_QUERIES, CONTEXT, CurrentMatchesList, DECLARATION_VIEW, DIRECTIVES, FLAGS, HEADER_OFFSET, HOST_NODE, INJECTOR, LViewData, LViewFlags, NEXT, OpaqueViewState, PARENT, QUERIES, RENDERER, RootContext, SANITIZER, TAIL, TData, TVIEW, TView} from './interfaces/view'; import {BINDING_INDEX, CLEANUP, CONTENT_QUERIES, CONTEXT, CurrentMatchesList, DECLARATION_VIEW, DIRECTIVES, FLAGS, HEADER_OFFSET, HOST_NODE, INJECTOR, LViewData, LViewFlags, NEXT, OpaqueViewState, PARENT, QUERIES, RENDERER, RootContext, SANITIZER, TAIL, TData, TVIEW, TView} from './interfaces/view';
import {assertNodeOfPossibleTypes, assertNodeType} from './node_assert'; import {assertNodeOfPossibleTypes, assertNodeType} from './node_assert';
import {appendChild, appendProjectedNode, canInsertNativeNode, createTextNode, findComponentHost, getChildLNode, getLViewChild, getNextLNode, getParentLNode, insertView, removeView} from './node_manipulation'; import {appendChild, appendProjectedNode, canInsertNativeNode, createTextNode, findComponentHost, getChildLNode, getLViewChild, getNextLNode, getParentLNode, insertView, removeView} from './node_manipulation';
@ -734,7 +734,7 @@ export function elementContainerStart(
createLNode(index, TNodeType.ElementContainer, native, null, attrs || null, null); createLNode(index, TNodeType.ElementContainer, native, null, attrs || null, null);
appendChild(getParentLNode(node), native, viewData); appendChild(getParentLNode(node), native, viewData);
createDirectivesAndLocals(localRefs); createDirectivesAndLocals(node, localRefs);
} }
/** Mark the end of the <ng-container>. */ /** Mark the end of the <ng-container>. */
@ -784,7 +784,7 @@ export function elementStart(
setUpAttributes(native, attrs); setUpAttributes(native, attrs);
} }
appendChild(getParentLNode(node), native, viewData); appendChild(getParentLNode(node), native, viewData);
createDirectivesAndLocals(localRefs); createDirectivesAndLocals(node, localRefs);
} }
/** /**
@ -809,21 +809,27 @@ export function elementCreate(name: string, overriddenRenderer?: Renderer3): REl
return native; return native;
} }
function nativeNodeLocalRefExtractor(lNode: LNodeWithLocalRefs): RNode {
return lNode.native;
}
/** /**
* Creates directive instances and populates local refs. * Creates directive instances and populates local refs.
* *
* @param localRefs Local refs of the current node * @param lNode LNode for which directive and locals should be created
* @param localRefs Local refs of the node in question
* @param localRefExtractor mapping function that extracts local ref value from LNode
*/ */
function createDirectivesAndLocals(localRefs?: string[] | null) { function createDirectivesAndLocals(
const node = previousOrParentNode; lNode: LNodeWithLocalRefs, localRefs: string[] | null | undefined,
localRefExtractor: LocalRefExtractor = nativeNodeLocalRefExtractor) {
if (firstTemplatePass) { if (firstTemplatePass) {
ngDevMode && ngDevMode.firstTemplatePass++; ngDevMode && ngDevMode.firstTemplatePass++;
cacheMatchingDirectivesForNode(node.tNode, tView, localRefs || null); cacheMatchingDirectivesForNode(lNode.tNode, tView, localRefs || null);
} else { } else {
instantiateDirectivesDirectly(); instantiateDirectivesDirectly();
} }
saveResolvedLocalsInData(); saveResolvedLocalsInData(lNode, localRefExtractor);
} }
/** /**
@ -976,12 +982,13 @@ function saveNameToExportMap(
* Takes a list of local names and indices and pushes the resolved local variable values * Takes a list of local names and indices and pushes the resolved local variable values
* to LViewData in the same order as they are loaded in the template with load(). * to LViewData in the same order as they are loaded in the template with load().
*/ */
function saveResolvedLocalsInData(): void { function saveResolvedLocalsInData(
const localNames = previousOrParentNode.tNode.localNames; lNode: LNodeWithLocalRefs, localRefExtractor: LocalRefExtractor): void {
const localNames = lNode.tNode.localNames;
if (localNames) { if (localNames) {
for (let i = 0; i < localNames.length; i += 2) { for (let i = 0; i < localNames.length; i += 2) {
const index = localNames[i + 1] as number; const index = localNames[i + 1] as number;
const value = index === -1 ? previousOrParentNode.native : directives ![index]; const value = index === -1 ? localRefExtractor(lNode) : directives ![index];
viewData.push(value); viewData.push(value);
} }
} }
@ -1838,10 +1845,13 @@ export function createLContainer(
* @param tagName The name of the container element, if applicable * @param tagName The name of the container element, if applicable
* @param attrs The attrs attached to the container, if applicable * @param attrs The attrs attached to the container, if applicable
* @param localRefs A set of local reference bindings on the element. * @param localRefs A set of local reference bindings on the element.
* @param localRefExtractor A function which extracts local-refs values from the template.
* Defaults to the current element associated with the local-ref.
*/ */
export function template( export function template(
index: number, templateFn: ComponentTemplate<any>| null, tagName?: string | null, index: number, templateFn: ComponentTemplate<any>| null, tagName?: string | null,
attrs?: TAttributes | null, localRefs?: string[] | null) { attrs?: TAttributes | null, localRefs?: string[] | null,
localRefExtractor?: LocalRefExtractor) {
// TODO: consider a separate node type for templates // TODO: consider a separate node type for templates
const node = containerInternal(index, tagName || null, attrs || null, localRefs || null); const node = containerInternal(index, tagName || null, attrs || null, localRefs || null);
if (firstTemplatePass) { if (firstTemplatePass) {
@ -1849,7 +1859,7 @@ export function template(
createTView(-1, templateFn, tView.directiveRegistry, tView.pipeRegistry, null); createTView(-1, templateFn, tView.directiveRegistry, tView.pipeRegistry, null);
} }
createDirectivesAndLocals(localRefs); createDirectivesAndLocals(node, localRefs, localRefExtractor);
currentQueries && (currentQueries = currentQueries.addNode(node)); currentQueries && (currentQueries = currentQueries.addNode(node));
queueLifecycleHooks(node.tNode.flags, tView); queueLifecycleHooks(node.tNode.flags, tView);
isParent = false; isParent = false;

View File

@ -524,3 +524,16 @@ export type InitialInputs = string[];
// Note: This hack is necessary so we don't erroneously get a circular dependency // Note: This hack is necessary so we don't erroneously get a circular dependency
// failure based on types. // failure based on types.
export const unusedValueExportToPlacateAjd = 1; export const unusedValueExportToPlacateAjd = 1;
/**
* Type representing a set of LNodes that can have local refs (`#foo`) placed on them.
*/
export type LNodeWithLocalRefs = LContainerNode | LElementNode | LElementContainerNode;
/**
* Type for a function that extracts a value for a local refs.
* Example:
* - `<div #nativeDivEl>` - `nativeDivEl` should point to the native `<div>` element;
* - `<ng-template #tplRef>` - `tplRef` should point to the `TemplateRef` instance;
*/
export type LocalRefExtractor = (lNode: LNodeWithLocalRefs) => any;

View File

@ -752,6 +752,9 @@
{ {
"name": "nativeInsertBefore" "name": "nativeInsertBefore"
}, },
{
"name": "nativeNodeLocalRefExtractor"
},
{ {
"name": "nextContext" "name": "nextContext"
}, },

View File

@ -8,8 +8,7 @@
import {NgForOfContext} from '@angular/common'; import {NgForOfContext} from '@angular/common';
import {getOrCreateNodeInjectorForNode, getOrCreateTemplateRef} from '../../src/render3/di'; import {AttributeMarker, defineComponent, templateRefExtractor} from '../../src/render3/index';
import {AttributeMarker, defineComponent} from '../../src/render3/index';
import {bind, template, elementEnd, elementProperty, elementStart, getCurrentView, interpolation1, interpolation2, interpolation3, interpolationV, listener, load, nextContext, restoreView, text, textBinding} from '../../src/render3/instructions'; import {bind, template, elementEnd, elementProperty, elementStart, getCurrentView, interpolation1, interpolation2, interpolation3, interpolationV, listener, load, nextContext, restoreView, text, textBinding} from '../../src/render3/instructions';
import {RenderFlags} from '../../src/render3/interfaces/definition'; import {RenderFlags} from '../../src/render3/interfaces/definition';
@ -881,11 +880,11 @@ describe('@angular/common integration', () => {
if (rf1 & RenderFlags.Create) { if (rf1 & RenderFlags.Create) {
text(0, 'from tpl'); text(0, 'from tpl');
} }
}, undefined, undefined, ['tpl', '']); }, undefined, undefined, ['tpl', ''], templateRefExtractor);
template(2, null, null, [AttributeMarker.SelectOnly, 'ngTemplateOutlet']); template(2, null, null, [AttributeMarker.SelectOnly, 'ngTemplateOutlet']);
} }
if (rf & RenderFlags.Update) { if (rf & RenderFlags.Update) {
const tplRef = getOrCreateTemplateRef(getOrCreateNodeInjectorForNode(load(0))); const tplRef = load(1);
elementProperty(2, 'ngTemplateOutlet', bind(myApp.showing ? tplRef : null)); elementProperty(2, 'ngTemplateOutlet', bind(myApp.showing ? tplRef : null));
} }
}, },

View File

@ -43,4 +43,42 @@ describe('local references', () => {
const fixture = new ComponentFixture(MyComponent); const fixture = new ComponentFixture(MyComponent);
expect(fixture.html).toEqual(`<input value="World">Hello, World!`); expect(fixture.html).toEqual(`<input value="World">Hello, World!`);
}); });
it('should expose TemplateRef when a local ref is placed on ng-template', () => {
type $MyComponent$ = MyComponent;
type $any$ = any;
@Component({
selector: 'my-component',
template: `<ng-template #tpl></ng-template>{{isTemplateRef(tpl)}}`
})
class MyComponent {
isTemplateRef(tplRef: any): boolean { return tplRef.createEmbeddedView != null; }
// NORMATIVE
static ngComponentDef = $r3$.ɵdefineComponent({
type: MyComponent,
selectors: [['my-component']],
factory: () => new MyComponent,
template: function(rf: $RenderFlags$, ctx: $MyComponent$) {
let l1_tpl: any;
if (rf & 1) {
$r3$.ɵtemplate(
0, MyComponent_Template_0, null, null, ['tpl', ''], $r3$.ɵtemplateRefExtractor);
$r3$.ɵtext(2);
}
if (rf & 2) {
l1_tpl = $r3$.ɵreference<any>(1);
$r3$.ɵtextBinding(2, $r3$.ɵinterpolation1('', ctx.isTemplateRef(l1_tpl), ''));
}
function MyComponent_Template_0(rf1: $RenderFlags$, ctx1: $any$) {}
}
});
// NORMATIVE
}
const fixture = new ComponentFixture(MyComponent);
expect(fixture.html).toEqual(`true`);
});
}); });

View File

@ -10,9 +10,9 @@ import {NgForOfContext} from '@angular/common';
import {ElementRef, TemplateRef, ViewContainerRef} from '@angular/core'; import {ElementRef, TemplateRef, ViewContainerRef} from '@angular/core';
import {EventEmitter} from '../..'; import {EventEmitter} from '../..';
import {QUERY_READ_CONTAINER_REF, QUERY_READ_ELEMENT_REF, QUERY_READ_FROM_NODE, QUERY_READ_TEMPLATE_REF, getOrCreateNodeInjectorForNode, getOrCreateTemplateRef} from '../../src/render3/di'; import {QUERY_READ_CONTAINER_REF, QUERY_READ_ELEMENT_REF, QUERY_READ_FROM_NODE, QUERY_READ_TEMPLATE_REF, templateRefExtractor} from '../../src/render3/di';
import {AttributeMarker, QueryList, defineComponent, defineDirective, detectChanges, injectTemplateRef, injectViewContainerRef} from '../../src/render3/index'; import {AttributeMarker, QueryList, defineComponent, defineDirective, detectChanges, injectTemplateRef, injectViewContainerRef} from '../../src/render3/index';
import {bind, container, containerRefreshEnd, containerRefreshStart, element, elementContainerEnd, elementContainerStart, elementEnd, elementProperty, elementStart, embeddedViewEnd, embeddedViewStart, load, loadDirective, loadElement, loadQueryList, registerContentQuery, template} from '../../src/render3/instructions'; import {bind, container, containerRefreshEnd, containerRefreshStart, element, elementContainerEnd, elementContainerStart, elementEnd, elementProperty, elementStart, embeddedViewEnd, embeddedViewStart, load, loadDirective, loadElement, loadQueryList, reference, registerContentQuery, template} from '../../src/render3/instructions';
import {RenderFlags} from '../../src/render3/interfaces/definition'; import {RenderFlags} from '../../src/render3/interfaces/definition';
import {query, queryRefresh} from '../../src/render3/query'; import {query, queryRefresh} from '../../src/render3/query';
@ -1116,25 +1116,25 @@ describe('query', () => {
if (rf & RenderFlags.Update) { if (rf & RenderFlags.Update) {
elementProperty(0, 'id', bind('foo1_' + ctx.idx)); elementProperty(0, 'id', bind('foo1_' + ctx.idx));
} }
}, null, []); }, null, null, ['tpl1', ''], templateRefExtractor);
element(2, 'div', ['id', 'middle'], ['foo', '']); element(3, 'div', ['id', 'middle'], ['foo', '']);
template(4, (rf: RenderFlags, ctx: {idx: number}) => { template(5, (rf: RenderFlags, ctx: {idx: number}) => {
if (rf & RenderFlags.Create) { if (rf & RenderFlags.Create) {
element(0, 'div', null, ['foo', '']); element(0, 'div', null, ['foo', '']);
} }
if (rf & RenderFlags.Update) { if (rf & RenderFlags.Update) {
elementProperty(0, 'id', bind('foo2_' + ctx.idx)); elementProperty(0, 'id', bind('foo2_' + ctx.idx));
} }
}, null, []); }, null, null, ['tpl2', ''], templateRefExtractor);
template(5, null, null, [AttributeMarker.SelectOnly, 'vc']); template(7, null, null, [AttributeMarker.SelectOnly, 'vc']);
} }
if (rf & RenderFlags.Update) { if (rf & RenderFlags.Update) {
tpl1 = getOrCreateTemplateRef(getOrCreateNodeInjectorForNode(load(1))); tpl1 = reference(2);
tpl2 = getOrCreateTemplateRef(getOrCreateNodeInjectorForNode(load(4))); tpl2 = reference(6);
} }
}, },
@ -1219,14 +1219,14 @@ describe('query', () => {
if (rf & RenderFlags.Update) { if (rf & RenderFlags.Update) {
elementProperty(0, 'id', bind('foo_' + ctx.container_idx + '_' + ctx.idx)); elementProperty(0, 'id', bind('foo_' + ctx.container_idx + '_' + ctx.idx));
} }
}, null, []); }, null, [], ['tpl', ''], templateRefExtractor);
template(2, null, null, [AttributeMarker.SelectOnly, 'vc']);
template(3, null, null, [AttributeMarker.SelectOnly, 'vc']); template(3, null, null, [AttributeMarker.SelectOnly, 'vc']);
template(4, null, null, [AttributeMarker.SelectOnly, 'vc']);
} }
if (rf & RenderFlags.Update) { if (rf & RenderFlags.Update) {
tpl = getOrCreateTemplateRef(getOrCreateNodeInjectorForNode(load(1))); tpl = reference(2);
} }
}, },
@ -1287,11 +1287,11 @@ describe('query', () => {
if (rf1 & RenderFlags.Create) { if (rf1 & RenderFlags.Create) {
element(0, 'span', ['id', 'from_tpl'], ['foo', '']); element(0, 'span', ['id', 'from_tpl'], ['foo', '']);
} }
}, undefined, undefined, ['tpl', '']); }, undefined, undefined, ['tpl', ''], templateRefExtractor);
template(3, null, null, [AttributeMarker.SelectOnly, 'ngTemplateOutlet']); template(3, null, null, [AttributeMarker.SelectOnly, 'ngTemplateOutlet']);
} }
if (rf & RenderFlags.Update) { if (rf & RenderFlags.Update) {
const tplRef = getOrCreateTemplateRef(getOrCreateNodeInjectorForNode(load(1))); const tplRef = reference(2);
elementProperty(3, 'ngTemplateOutlet', bind(myApp.show ? tplRef : null)); elementProperty(3, 'ngTemplateOutlet', bind(myApp.show ? tplRef : null));
} }
}, },

View File

@ -7,9 +7,9 @@
*/ */
import {Component, ComponentFactoryResolver, ElementRef, EmbeddedViewRef, NgModuleRef, Pipe, PipeTransform, RendererFactory2, TemplateRef, ViewContainerRef, createInjector, defineInjector, ɵAPP_ROOT as APP_ROOT, ɵNgModuleDef as NgModuleDef} from '../../src/core'; import {Component, ComponentFactoryResolver, ElementRef, EmbeddedViewRef, NgModuleRef, Pipe, PipeTransform, RendererFactory2, TemplateRef, ViewContainerRef, createInjector, defineInjector, ɵAPP_ROOT as APP_ROOT, ɵNgModuleDef as NgModuleDef} from '../../src/core';
import {getOrCreateNodeInjectorForNode, getOrCreateTemplateRef} from '../../src/render3/di'; import {templateRefExtractor} from '../../src/render3/di';
import {AttributeMarker, NgOnChangesFeature, defineComponent, defineDirective, definePipe, injectComponentFactoryResolver, injectTemplateRef, injectViewContainerRef} from '../../src/render3/index'; import {AttributeMarker, NgOnChangesFeature, defineComponent, defineDirective, definePipe, injectComponentFactoryResolver, injectTemplateRef, injectViewContainerRef} from '../../src/render3/index';
import {bind, container, containerRefreshEnd, containerRefreshStart, element, elementEnd, elementProperty, elementStart, embeddedViewEnd, embeddedViewStart, interpolation1, interpolation3, load, loadDirective, nextContext, projection, projectionDef, reserveSlots, template, text, textBinding} from '../../src/render3/instructions'; import {bind, container, containerRefreshEnd, containerRefreshStart, element, elementEnd, elementProperty, elementStart, embeddedViewEnd, embeddedViewStart, interpolation1, interpolation3, loadDirective, nextContext, projection, projectionDef, reference, reserveSlots, template, text, textBinding} from '../../src/render3/instructions';
import {RenderFlags} from '../../src/render3/interfaces/definition'; import {RenderFlags} from '../../src/render3/interfaces/definition';
import {NgModuleFactory} from '../../src/render3/ng_module_ref'; import {NgModuleFactory} from '../../src/render3/ng_module_ref';
import {pipe, pipeBind1} from '../../src/render3/pipe'; import {pipe, pipeBind1} from '../../src/render3/pipe';
@ -41,6 +41,9 @@ describe('ViewContainerRef', () => {
} }
describe('API', () => { describe('API', () => {
/**
* {{name}}
*/
function embeddedTemplate(rf: RenderFlags, ctx: any) { function embeddedTemplate(rf: RenderFlags, ctx: any) {
if (rf & RenderFlags.Create) { if (rf & RenderFlags.Create) {
text(0); text(0);
@ -56,28 +59,30 @@ describe('ViewContainerRef', () => {
} }
/** /**
* <ng-template #foo> * <ng-template #tplRef>{{name}}</ng-template>
* {{name}} * <p vcref [tplRef]="tplRef"></p>
* </ng-template>
* <p vcref="" [tplRef]="foo">
* </p>
*/ */
function createTemplate() { function createTemplate() {
template(0, embeddedTemplate); template(0, embeddedTemplate, null, null, ['tplRef', ''], templateRefExtractor);
element(1, 'p', ['vcref', '']); element(2, 'p', ['vcref', '']);
} }
function updateTemplate() { function updateTemplate() {
const tplRef = getOrCreateTemplateRef(getOrCreateNodeInjectorForNode(load(0))); const tplRef = reference(1);
elementProperty(1, 'tplRef', bind(tplRef)); elementProperty(2, 'tplRef', bind(tplRef));
} }
describe('createEmbeddedView (incl. insert)', () => { describe('createEmbeddedView (incl. insert)', () => {
it('should work on elements', () => { it('should work on elements', () => {
/**
* <ng-template #tplRef>{{name}}</ng-template>
* <header vcref [tplRef]="tplRef"></header>
* <footer></footer>
*/
function createTemplate() { function createTemplate() {
template(0, embeddedTemplate); template(0, embeddedTemplate, null, null, ['tplRef', ''], templateRefExtractor);
element(1, 'header', ['vcref', '']); element(2, 'header', ['vcref', '']);
element(2, 'footer'); element(3, 'footer');
} }
const fixture = new TemplateFixture(createTemplate, updateTemplate, [DirectiveWithVCRef]); const fixture = new TemplateFixture(createTemplate, updateTemplate, [DirectiveWithVCRef]);
@ -104,10 +109,15 @@ describe('ViewContainerRef', () => {
const HeaderComponent = const HeaderComponent =
createComponent('header-cmp', function(rf: RenderFlags, ctx: any) {}); createComponent('header-cmp', function(rf: RenderFlags, ctx: any) {});
/**
* <ng-template #tplRef>{{name}}</ng-template>
* <header-cmp vcref [tplRef]="tplRef"></header-cmp>
* <footer></footer>
*/
function createTemplate() { function createTemplate() {
template(0, embeddedTemplate); template(0, embeddedTemplate, null, [], ['tplRef', ''], templateRefExtractor);
element(1, 'header-cmp', ['vcref', '']); element(2, 'header-cmp', ['vcref', '']);
element(2, 'footer'); element(3, 'footer');
} }
const fixture = new TemplateFixture( const fixture = new TemplateFixture(
@ -135,10 +145,15 @@ describe('ViewContainerRef', () => {
let firstDir: DirectiveWithVCRef; let firstDir: DirectiveWithVCRef;
let secondDir: DirectiveWithVCRef; let secondDir: DirectiveWithVCRef;
/**
* <ng-template #tplRef>{{name}}</ng-template>
* <div vcref [tplRef]="tplRef"></div>
* <div vcref [tplRef]="tplRef"></div>
*/
function createTemplate() { function createTemplate() {
template(0, embeddedTemplate); template(0, embeddedTemplate, null, null, ['tplRef', ''], templateRefExtractor);
element(1, 'div', ['vcref', '']);
element(2, 'div', ['vcref', '']); element(2, 'div', ['vcref', '']);
element(3, 'div', ['vcref', '']);
// for testing only: // for testing only:
firstDir = loadDirective(0); firstDir = loadDirective(0);
@ -146,10 +161,9 @@ describe('ViewContainerRef', () => {
} }
function update() { function update() {
// Hack until we can create local refs to templates const tplRef = reference(1);
const tplRef = getOrCreateTemplateRef(getOrCreateNodeInjectorForNode(load(0)));
elementProperty(1, 'tplRef', bind(tplRef));
elementProperty(2, 'tplRef', bind(tplRef)); elementProperty(2, 'tplRef', bind(tplRef));
elementProperty(3, 'tplRef', bind(tplRef));
} }
const fixture = new TemplateFixture(createTemplate, update, [DirectiveWithVCRef]); const fixture = new TemplateFixture(createTemplate, update, [DirectiveWithVCRef]);
@ -162,16 +176,18 @@ describe('ViewContainerRef', () => {
}); });
it('should work on templates', () => { it('should work on templates', () => {
/**
* <ng-template vcref #tplRef>{{name}}</ng-template>
* <footer></footer>
*/
function createTemplate() { function createTemplate() {
template(0, embeddedTemplate, null, ['vcref', '']); template(0, embeddedTemplate, null, ['vcref', ''], ['tplRef', ''], templateRefExtractor);
element(1, 'footer'); element(2, 'footer');
} }
function updateTemplate() { function updateTemplate() {
const tplRef = getOrCreateTemplateRef(getOrCreateNodeInjectorForNode(load(0))); const tplRef = reference(1);
elementProperty(0, 'tplRef', bind(tplRef)); elementProperty(0, 'tplRef', bind(tplRef));
containerRefreshStart(0);
containerRefreshEnd();
} }
const fixture = new TemplateFixture(createTemplate, updateTemplate, [DirectiveWithVCRef]); const fixture = new TemplateFixture(createTemplate, updateTemplate, [DirectiveWithVCRef]);
@ -419,18 +435,18 @@ describe('ViewContainerRef', () => {
if (rf & RenderFlags.Update) { if (rf & RenderFlags.Update) {
elementProperty(0, 'name', bind(pipeBind1(1, 2, 'C'))); elementProperty(0, 'name', bind(pipeBind1(1, 2, 'C')));
} }
}); }, null, [], ['foo', ''], templateRefExtractor);
pipe(1, 'starPipe'); pipe(2, 'starPipe');
element(2, 'child', ['vcref', '']); element(3, 'child', ['vcref', '']);
pipe(3, 'starPipe'); pipe(4, 'starPipe');
element(4, 'child'); element(5, 'child');
reserveSlots(4); reserveSlots(4);
} }
if (rf & RenderFlags.Update) { if (rf & RenderFlags.Update) {
const tplRef = getOrCreateTemplateRef(getOrCreateNodeInjectorForNode(load(0))); const tplRef = reference(1);
elementProperty(2, 'tplRef', bind(tplRef)); elementProperty(3, 'tplRef', bind(tplRef));
elementProperty(2, 'name', bind(pipeBind1(1, 2, 'A'))); elementProperty(3, 'name', bind(pipeBind1(2, 2, 'A')));
elementProperty(4, 'name', bind(pipeBind1(1, 4, 'B'))); elementProperty(5, 'name', bind(pipeBind1(4, 4, 'B')));
} }
}, },
directives: [Child, DirectiveWithVCRef], directives: [Child, DirectiveWithVCRef],
@ -508,15 +524,13 @@ describe('ViewContainerRef', () => {
*/ */
const Parent = createComponent('parent', function(rf: RenderFlags, parent: any) { const Parent = createComponent('parent', function(rf: RenderFlags, parent: any) {
if (rf & RenderFlags.Create) { if (rf & RenderFlags.Create) {
template(0, fooTemplate); template(0, fooTemplate, null, null, ['foo', ''], templateRefExtractor);
elementStart(1, 'child'); element(2, 'child');
elementEnd();
} }
if (rf & RenderFlags.Update) { if (rf & RenderFlags.Update) {
// Hack until we have local refs for templates const tplRef = reference(1);
const tplRef = getOrCreateTemplateRef(getOrCreateNodeInjectorForNode(load(0))); elementProperty(2, 'tpl', bind(tplRef));
elementProperty(1, 'tpl', bind(tplRef));
} }
}, [Child]); }, [Child]);
@ -599,33 +613,29 @@ describe('ViewContainerRef', () => {
*/ */
const Parent = createComponent('parent', function(rf: RenderFlags, parent: any) { const Parent = createComponent('parent', function(rf: RenderFlags, parent: any) {
if (rf & RenderFlags.Create) { if (rf & RenderFlags.Create) {
template(0, rowTemplate); template(0, rowTemplate, null, null, ['rowTemplate', ''], templateRefExtractor);
elementStart(1, 'loop-comp'); element(2, 'loop-comp');
elementEnd();
} }
if (rf & RenderFlags.Update) { if (rf & RenderFlags.Update) {
// Hack until we have local refs for templates const rowTemplateRef = reference(1);
const rowTemplateRef = getOrCreateTemplateRef(getOrCreateNodeInjectorForNode(load(0))); elementProperty(2, 'tpl', bind(rowTemplateRef));
elementProperty(1, 'tpl', bind(rowTemplateRef)); elementProperty(2, 'rows', bind(parent.rows));
elementProperty(1, 'rows', bind(parent.rows));
} }
}, [LoopComp]); }, [LoopComp]);
function rowTemplate(rf: RenderFlags, ctx: any) { function rowTemplate(rf: RenderFlags, ctx: any) {
if (rf & RenderFlags.Create) { if (rf & RenderFlags.Create) {
template(0, cellTemplate); template(0, cellTemplate, null, null, ['cellTemplate', ''], templateRefExtractor);
elementStart(1, 'loop-comp'); element(2, 'loop-comp');
elementEnd();
} }
if (rf & RenderFlags.Update) { if (rf & RenderFlags.Update) {
const row = ctx.$implicit as any; const row = ctx.$implicit as any;
// Hack until we have local refs for templates const cellTemplateRef = reference(1);
const cellTemplateRef = getOrCreateTemplateRef(getOrCreateNodeInjectorForNode(load(0))); elementProperty(2, 'tpl', bind(cellTemplateRef));
elementProperty(1, 'tpl', bind(cellTemplateRef)); elementProperty(2, 'rows', bind(row.data));
elementProperty(1, 'rows', bind(row.data));
} }
} }
@ -1131,18 +1141,20 @@ describe('ViewContainerRef', () => {
factory: () => new Parent(), factory: () => new Parent(),
template: (rf: RenderFlags, cmp: Parent) => { template: (rf: RenderFlags, cmp: Parent) => {
if (rf & RenderFlags.Create) { if (rf & RenderFlags.Create) {
template(0, embeddedTemplate); template(0, embeddedTemplate, null, null, ['foo', ''], templateRefExtractor);
elementStart(1, 'child'); elementStart(2, 'child');
elementStart(2, 'header', ['vcref', '']); {
text(3, 'blah'); elementStart(3, 'header', ['vcref', '']);
elementEnd(); { text(4, 'blah'); }
elementEnd();
}
elementEnd(); elementEnd();
} }
let tplRef: any; let tplRef: any;
if (rf & RenderFlags.Update) { if (rf & RenderFlags.Update) {
tplRef = getOrCreateTemplateRef(getOrCreateNodeInjectorForNode(load(0))); tplRef = reference(1);
elementProperty(2, 'tplRef', bind(tplRef)); elementProperty(3, 'tplRef', bind(tplRef));
elementProperty(2, 'name', bind(cmp.name)); elementProperty(3, 'name', bind(cmp.name));
} }
}, },
directives: [Child, DirectiveWithVCRef] directives: [Child, DirectiveWithVCRef]
@ -1217,19 +1229,19 @@ describe('ViewContainerRef', () => {
factory: () => new Parent(), factory: () => new Parent(),
template: (rf: RenderFlags, cmp: Parent) => { template: (rf: RenderFlags, cmp: Parent) => {
if (rf & RenderFlags.Create) { if (rf & RenderFlags.Create) {
template(0, embeddedTemplate); template(0, embeddedTemplate, null, undefined, ['foo', ''], templateRefExtractor);
elementStart(1, 'child-with-view'); elementStart(2, 'child-with-view');
text(2, 'Before projected'); text(3, 'Before projected');
elementStart(3, 'header', ['vcref', '']); elementStart(4, 'header', ['vcref', '']);
text(4, 'blah'); text(5, 'blah');
elementEnd(); elementEnd();
text(5, 'After projected-'); text(6, 'After projected-');
elementEnd(); elementEnd();
} }
if (rf & RenderFlags.Update) { if (rf & RenderFlags.Update) {
const tplRef = getOrCreateTemplateRef(getOrCreateNodeInjectorForNode(load(0))); const tplRef = reference(1);
elementProperty(3, 'tplRef', bind(tplRef)); elementProperty(4, 'tplRef', bind(tplRef));
elementProperty(3, 'name', bind(cmp.name)); elementProperty(4, 'name', bind(cmp.name));
} }
}, },
directives: [ChildWithView, DirectiveWithVCRef] directives: [ChildWithView, DirectiveWithVCRef]
@ -1294,17 +1306,17 @@ describe('ViewContainerRef', () => {
template: (rf: RenderFlags, cmp: Parent) => { template: (rf: RenderFlags, cmp: Parent) => {
let tplRef: any; let tplRef: any;
if (rf & RenderFlags.Create) { if (rf & RenderFlags.Create) {
template(0, embeddedTemplate); template(0, embeddedTemplate, null, null, ['foo', ''], templateRefExtractor);
elementStart(1, 'child-with-selector'); elementStart(2, 'child-with-selector');
elementStart(2, 'header', ['vcref', '']); elementStart(3, 'header', ['vcref', '']);
text(3, 'blah'); text(4, 'blah');
elementEnd(); elementEnd();
elementEnd(); elementEnd();
} }
if (rf & RenderFlags.Update) { if (rf & RenderFlags.Update) {
tplRef = getOrCreateTemplateRef(getOrCreateNodeInjectorForNode(load(0))); tplRef = reference(1);
elementProperty(2, 'tplRef', bind(tplRef)); elementProperty(3, 'tplRef', bind(tplRef));
elementProperty(2, 'name', bind(cmp.name)); elementProperty(3, 'name', bind(cmp.name));
} }
}, },
directives: [ChildWithSelector, DirectiveWithVCRef] directives: [ChildWithSelector, DirectiveWithVCRef]
@ -1343,17 +1355,17 @@ describe('ViewContainerRef', () => {
template: (rf: RenderFlags, cmp: Parent) => { template: (rf: RenderFlags, cmp: Parent) => {
let tplRef: any; let tplRef: any;
if (rf & RenderFlags.Create) { if (rf & RenderFlags.Create) {
template(0, embeddedTemplate); template(0, embeddedTemplate, null, null, ['foo', ''], templateRefExtractor);
elementStart(1, 'child-with-selector'); elementStart(2, 'child-with-selector');
elementStart(2, 'footer', ['vcref', '']); elementStart(3, 'footer', ['vcref', '']);
text(3, 'blah'); text(4, 'blah');
elementEnd(); elementEnd();
elementEnd(); elementEnd();
} }
if (rf & RenderFlags.Update) { if (rf & RenderFlags.Update) {
tplRef = getOrCreateTemplateRef(getOrCreateNodeInjectorForNode(load(0))); tplRef = reference(1);
elementProperty(2, 'tplRef', bind(tplRef)); elementProperty(3, 'tplRef', bind(tplRef));
elementProperty(2, 'name', bind(cmp.name)); elementProperty(3, 'name', bind(cmp.name));
} }
}, },
directives: [ChildWithSelector, DirectiveWithVCRef] directives: [ChildWithSelector, DirectiveWithVCRef]
@ -1440,15 +1452,15 @@ describe('ViewContainerRef', () => {
if (rf & RenderFlags.Update) { if (rf & RenderFlags.Update) {
elementProperty(0, 'name', bind('C')); elementProperty(0, 'name', bind('C'));
} }
}); }, null, [], ['foo', ''], templateRefExtractor);
element(1, 'hooks', ['vcref', '']); element(2, 'hooks', ['vcref', '']);
element(2, 'hooks'); element(3, 'hooks');
} }
if (rf & RenderFlags.Update) { if (rf & RenderFlags.Update) {
const tplRef = getOrCreateTemplateRef(getOrCreateNodeInjectorForNode(load(0))); const tplRef = reference(1);
elementProperty(1, 'tplRef', bind(tplRef)); elementProperty(2, 'tplRef', bind(tplRef));
elementProperty(1, 'name', bind('A')); elementProperty(2, 'name', bind('A'));
elementProperty(2, 'name', bind('B')); elementProperty(3, 'name', bind('B'));
} }
}, },
directives: [ComponentWithHooks, DirectiveWithVCRef] directives: [ComponentWithHooks, DirectiveWithVCRef]