Skip to content

Commit 31f0f5b

Browse files
pkozlowski-opensourcejasonaden
authored andcommitted
feat(ivy): add support for local refs on ng-template (angular#25482)
PR Close angular#25482
1 parent 07d8d39 commit 31f0f5b

10 files changed

Lines changed: 213 additions & 129 deletions

File tree

packages/core/src/core_render3_private_export.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,7 @@ export {
2828
injectAttribute as ɵinjectAttribute,
2929
getFactoryOf as ɵgetFactoryOf,
3030
getInheritedFactory as ɵgetInheritedFactory,
31+
templateRefExtractor as ɵtemplateRefExtractor,
3132
PublicFeature as ɵPublicFeature,
3233
InheritDefinitionFeature as ɵInheritDefinitionFeature,
3334
NgOnChangesFeature as ɵNgOnChangesFeature,

packages/core/src/render3/di.ts

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,7 @@ import {addToViewTree, assertPreviousIsParent, createEmbeddedViewNode, createLCo
2727
import {VIEWS} from './interfaces/container';
2828
import {DirectiveDefInternal, RenderFlags} from './interfaces/definition';
2929
import {LInjector} from './interfaces/injector';
30-
import {AttributeMarker, LContainerNode, LElementContainerNode, LElementNode, LNode, LViewNode, TContainerNode, TElementNode, TNodeFlags, TNodeType} from './interfaces/node';
30+
import {AttributeMarker, LContainerNode, LElementContainerNode, LElementNode, LNode, LNodeWithLocalRefs, LViewNode, TContainerNode, TElementNode, TNodeFlags, TNodeType} from './interfaces/node';
3131
import {LQueries, QueryReadType} from './interfaces/query';
3232
import {Renderer3} from './interfaces/renderer';
3333
import {DIRECTIVES, HOST_NODE, INJECTOR, LViewData, QUERIES, RENDERER, TVIEW, TView} from './interfaces/view';
@@ -814,3 +814,11 @@ class TemplateRef<T> implements viewEngine_TemplateRef<T> {
814814
return viewRef;
815815
}
816816
}
817+
818+
/**
819+
* Retrieves `TemplateRef` instance from `Injector` when a local reference is placed on the
820+
* `<ng-template>` element.
821+
*/
822+
export function templateRefExtractor(lNode: LNodeWithLocalRefs) {
823+
return getOrCreateTemplateRef(getOrCreateNodeInjectorForNode(lNode));
824+
}

packages/core/src/render3/index.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@ import {PublicFeature} from './features/public_feature';
1414
import {BaseDef, ComponentDef, ComponentDefInternal, ComponentTemplate, ComponentType, DirectiveDef, DirectiveDefFlags, DirectiveDefInternal, DirectiveType, PipeDef} from './interfaces/definition';
1515

1616
export {ComponentFactory, ComponentFactoryResolver, ComponentRef, WRAP_RENDERER_FACTORY2} from './component_ref';
17-
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';
17+
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';
1818
export {RenderFlags} from './interfaces/definition';
1919
export {CssSelectorList} from './interfaces/projection';
2020

packages/core/src/render3/instructions.ts

Lines changed: 25 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -18,10 +18,10 @@ import {executeHooks, executeInitHooks, queueInitHooks, queueLifecycleHooks} fro
1818
import {ACTIVE_INDEX, LContainer, RENDER_PARENT, VIEWS} from './interfaces/container';
1919
import {ComponentDefInternal, ComponentQuery, ComponentTemplate, DirectiveDefInternal, DirectiveDefListOrFactory, InitialStylingFlags, PipeDefListOrFactory, RenderFlags} from './interfaces/definition';
2020
import {LInjector} from './interfaces/injector';
21-
import {AttributeMarker, InitialInputData, InitialInputs, LContainerNode, LElementContainerNode, LElementNode, LNode, LProjectionNode, LTextNode, LViewNode, PropertyAliasValue, PropertyAliases, TAttributes, TContainerNode, TElementNode, TNode, TNodeFlags, TNodeType} from './interfaces/node';
21+
import {AttributeMarker, InitialInputData, InitialInputs, LContainerNode, LElementContainerNode, LElementNode, LNode, LNodeWithLocalRefs, LProjectionNode, LTextNode, LViewNode, LocalRefExtractor, PropertyAliasValue, PropertyAliases, TAttributes, TContainerNode, TElementNode, TNode, TNodeFlags, TNodeType} from './interfaces/node';
2222
import {CssSelectorList, NG_PROJECT_AS_ATTR_NAME} from './interfaces/projection';
2323
import {LQueries} from './interfaces/query';
24-
import {ProceduralRenderer3, RComment, RElement, RText, Renderer3, RendererFactory3, RendererStyleFlags3, isProceduralRenderer} from './interfaces/renderer';
24+
import {ProceduralRenderer3, RComment, RElement, RNode, RText, Renderer3, RendererFactory3, RendererStyleFlags3, isProceduralRenderer} from './interfaces/renderer';
2525
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';
2626
import {assertNodeOfPossibleTypes, assertNodeType} from './node_assert';
2727
import {appendChild, appendProjectedNode, canInsertNativeNode, createTextNode, findComponentHost, getChildLNode, getLViewChild, getNextLNode, getParentLNode, insertView, removeView} from './node_manipulation';
@@ -734,7 +734,7 @@ export function elementContainerStart(
734734
createLNode(index, TNodeType.ElementContainer, native, null, attrs || null, null);
735735

736736
appendChild(getParentLNode(node), native, viewData);
737-
createDirectivesAndLocals(localRefs);
737+
createDirectivesAndLocals(node, localRefs);
738738
}
739739

740740
/** Mark the end of the <ng-container>. */
@@ -784,7 +784,7 @@ export function elementStart(
784784
setUpAttributes(native, attrs);
785785
}
786786
appendChild(getParentLNode(node), native, viewData);
787-
createDirectivesAndLocals(localRefs);
787+
createDirectivesAndLocals(node, localRefs);
788788
}
789789

790790
/**
@@ -809,21 +809,27 @@ export function elementCreate(name: string, overriddenRenderer?: Renderer3): REl
809809
return native;
810810
}
811811

812+
function nativeNodeLocalRefExtractor(lNode: LNodeWithLocalRefs): RNode {
813+
return lNode.native;
814+
}
815+
812816
/**
813817
* Creates directive instances and populates local refs.
814818
*
815-
* @param localRefs Local refs of the current node
819+
* @param lNode LNode for which directive and locals should be created
820+
* @param localRefs Local refs of the node in question
821+
* @param localRefExtractor mapping function that extracts local ref value from LNode
816822
*/
817-
function createDirectivesAndLocals(localRefs?: string[] | null) {
818-
const node = previousOrParentNode;
819-
823+
function createDirectivesAndLocals(
824+
lNode: LNodeWithLocalRefs, localRefs: string[] | null | undefined,
825+
localRefExtractor: LocalRefExtractor = nativeNodeLocalRefExtractor) {
820826
if (firstTemplatePass) {
821827
ngDevMode && ngDevMode.firstTemplatePass++;
822-
cacheMatchingDirectivesForNode(node.tNode, tView, localRefs || null);
828+
cacheMatchingDirectivesForNode(lNode.tNode, tView, localRefs || null);
823829
} else {
824830
instantiateDirectivesDirectly();
825831
}
826-
saveResolvedLocalsInData();
832+
saveResolvedLocalsInData(lNode, localRefExtractor);
827833
}
828834

829835
/**
@@ -976,12 +982,13 @@ function saveNameToExportMap(
976982
* Takes a list of local names and indices and pushes the resolved local variable values
977983
* to LViewData in the same order as they are loaded in the template with load().
978984
*/
979-
function saveResolvedLocalsInData(): void {
980-
const localNames = previousOrParentNode.tNode.localNames;
985+
function saveResolvedLocalsInData(
986+
lNode: LNodeWithLocalRefs, localRefExtractor: LocalRefExtractor): void {
987+
const localNames = lNode.tNode.localNames;
981988
if (localNames) {
982989
for (let i = 0; i < localNames.length; i += 2) {
983990
const index = localNames[i + 1] as number;
984-
const value = index === -1 ? previousOrParentNode.native : directives ![index];
991+
const value = index === -1 ? localRefExtractor(lNode) : directives ![index];
985992
viewData.push(value);
986993
}
987994
}
@@ -1838,18 +1845,21 @@ export function createLContainer(
18381845
* @param tagName The name of the container element, if applicable
18391846
* @param attrs The attrs attached to the container, if applicable
18401847
* @param localRefs A set of local reference bindings on the element.
1848+
* @param localRefExtractor A function which extracts local-refs values from the template.
1849+
* Defaults to the current element associated with the local-ref.
18411850
*/
18421851
export function template(
18431852
index: number, templateFn: ComponentTemplate<any>| null, tagName?: string | null,
1844-
attrs?: TAttributes | null, localRefs?: string[] | null) {
1853+
attrs?: TAttributes | null, localRefs?: string[] | null,
1854+
localRefExtractor?: LocalRefExtractor) {
18451855
// TODO: consider a separate node type for templates
18461856
const node = containerInternal(index, tagName || null, attrs || null, localRefs || null);
18471857
if (firstTemplatePass) {
18481858
node.tNode.tViews =
18491859
createTView(-1, templateFn, tView.directiveRegistry, tView.pipeRegistry, null);
18501860
}
18511861

1852-
createDirectivesAndLocals(localRefs);
1862+
createDirectivesAndLocals(node, localRefs, localRefExtractor);
18531863
currentQueries && (currentQueries = currentQueries.addNode(node));
18541864
queueLifecycleHooks(node.tNode.flags, tView);
18551865
isParent = false;

packages/core/src/render3/interfaces/node.ts

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -524,3 +524,16 @@ export type InitialInputs = string[];
524524
// Note: This hack is necessary so we don't erroneously get a circular dependency
525525
// failure based on types.
526526
export const unusedValueExportToPlacateAjd = 1;
527+
528+
/**
529+
* Type representing a set of LNodes that can have local refs (`#foo`) placed on them.
530+
*/
531+
export type LNodeWithLocalRefs = LContainerNode | LElementNode | LElementContainerNode;
532+
533+
/**
534+
* Type for a function that extracts a value for a local refs.
535+
* Example:
536+
* - `<div #nativeDivEl>` - `nativeDivEl` should point to the native `<div>` element;
537+
* - `<ng-template #tplRef>` - `tplRef` should point to the `TemplateRef` instance;
538+
*/
539+
export type LocalRefExtractor = (lNode: LNodeWithLocalRefs) => any;

packages/core/test/bundling/todo/bundle.golden_symbols.json

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -752,6 +752,9 @@
752752
{
753753
"name": "nativeInsertBefore"
754754
},
755+
{
756+
"name": "nativeNodeLocalRefExtractor"
757+
},
755758
{
756759
"name": "nextContext"
757760
},

packages/core/test/render3/common_integration_spec.ts

Lines changed: 3 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -8,8 +8,7 @@
88

99
import {NgForOfContext} from '@angular/common';
1010

11-
import {getOrCreateNodeInjectorForNode, getOrCreateTemplateRef} from '../../src/render3/di';
12-
import {AttributeMarker, defineComponent} from '../../src/render3/index';
11+
import {AttributeMarker, defineComponent, templateRefExtractor} from '../../src/render3/index';
1312
import {bind, template, elementEnd, elementProperty, elementStart, getCurrentView, interpolation1, interpolation2, interpolation3, interpolationV, listener, load, nextContext, restoreView, text, textBinding} from '../../src/render3/instructions';
1413
import {RenderFlags} from '../../src/render3/interfaces/definition';
1514

@@ -881,11 +880,11 @@ describe('@angular/common integration', () => {
881880
if (rf1 & RenderFlags.Create) {
882881
text(0, 'from tpl');
883882
}
884-
}, undefined, undefined, ['tpl', '']);
883+
}, undefined, undefined, ['tpl', ''], templateRefExtractor);
885884
template(2, null, null, [AttributeMarker.SelectOnly, 'ngTemplateOutlet']);
886885
}
887886
if (rf & RenderFlags.Update) {
888-
const tplRef = getOrCreateTemplateRef(getOrCreateNodeInjectorForNode(load(0)));
887+
const tplRef = load(1);
889888
elementProperty(2, 'ngTemplateOutlet', bind(myApp.showing ? tplRef : null));
890889
}
891890
},

packages/core/test/render3/compiler_canonical/local_reference_spec.ts

Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -43,4 +43,42 @@ describe('local references', () => {
4343
const fixture = new ComponentFixture(MyComponent);
4444
expect(fixture.html).toEqual(`<input value="World">Hello, World!`);
4545
});
46+
47+
it('should expose TemplateRef when a local ref is placed on ng-template', () => {
48+
type $MyComponent$ = MyComponent;
49+
type $any$ = any;
50+
51+
@Component({
52+
selector: 'my-component',
53+
template: `<ng-template #tpl></ng-template>{{isTemplateRef(tpl)}}`
54+
})
55+
class MyComponent {
56+
isTemplateRef(tplRef: any): boolean { return tplRef.createEmbeddedView != null; }
57+
58+
// NORMATIVE
59+
static ngComponentDef = $r3$.ɵdefineComponent({
60+
type: MyComponent,
61+
selectors: [['my-component']],
62+
factory: () => new MyComponent,
63+
template: function(rf: $RenderFlags$, ctx: $MyComponent$) {
64+
let l1_tpl: any;
65+
if (rf & 1) {
66+
$r3$.ɵtemplate(
67+
0, MyComponent_Template_0, null, null, ['tpl', ''], $r3$.ɵtemplateRefExtractor);
68+
$r3$.ɵtext(2);
69+
}
70+
if (rf & 2) {
71+
l1_tpl = $r3$.ɵreference<any>(1);
72+
$r3$.ɵtextBinding(2, $r3$.ɵinterpolation1('', ctx.isTemplateRef(l1_tpl), ''));
73+
}
74+
75+
function MyComponent_Template_0(rf1: $RenderFlags$, ctx1: $any$) {}
76+
}
77+
});
78+
// NORMATIVE
79+
}
80+
81+
const fixture = new ComponentFixture(MyComponent);
82+
expect(fixture.html).toEqual(`true`);
83+
});
4684
});

packages/core/test/render3/query_spec.ts

Lines changed: 14 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -10,9 +10,9 @@ import {NgForOfContext} from '@angular/common';
1010
import {ElementRef, TemplateRef, ViewContainerRef} from '@angular/core';
1111

1212
import {EventEmitter} from '../..';
13-
import {QUERY_READ_CONTAINER_REF, QUERY_READ_ELEMENT_REF, QUERY_READ_FROM_NODE, QUERY_READ_TEMPLATE_REF, getOrCreateNodeInjectorForNode, getOrCreateTemplateRef} from '../../src/render3/di';
13+
import {QUERY_READ_CONTAINER_REF, QUERY_READ_ELEMENT_REF, QUERY_READ_FROM_NODE, QUERY_READ_TEMPLATE_REF, templateRefExtractor} from '../../src/render3/di';
1414
import {AttributeMarker, QueryList, defineComponent, defineDirective, detectChanges, injectTemplateRef, injectViewContainerRef} from '../../src/render3/index';
15-
import {bind, container, containerRefreshEnd, containerRefreshStart, element, elementContainerEnd, elementContainerStart, elementEnd, elementProperty, elementStart, embeddedViewEnd, embeddedViewStart, load, loadDirective, loadElement, loadQueryList, registerContentQuery, template} from '../../src/render3/instructions';
15+
import {bind, container, containerRefreshEnd, containerRefreshStart, element, elementContainerEnd, elementContainerStart, elementEnd, elementProperty, elementStart, embeddedViewEnd, embeddedViewStart, load, loadDirective, loadElement, loadQueryList, reference, registerContentQuery, template} from '../../src/render3/instructions';
1616
import {RenderFlags} from '../../src/render3/interfaces/definition';
1717
import {query, queryRefresh} from '../../src/render3/query';
1818

@@ -1116,25 +1116,25 @@ describe('query', () => {
11161116
if (rf & RenderFlags.Update) {
11171117
elementProperty(0, 'id', bind('foo1_' + ctx.idx));
11181118
}
1119-
}, null, []);
1119+
}, null, null, ['tpl1', ''], templateRefExtractor);
11201120

1121-
element(2, 'div', ['id', 'middle'], ['foo', '']);
1121+
element(3, 'div', ['id', 'middle'], ['foo', '']);
11221122

1123-
template(4, (rf: RenderFlags, ctx: {idx: number}) => {
1123+
template(5, (rf: RenderFlags, ctx: {idx: number}) => {
11241124
if (rf & RenderFlags.Create) {
11251125
element(0, 'div', null, ['foo', '']);
11261126
}
11271127
if (rf & RenderFlags.Update) {
11281128
elementProperty(0, 'id', bind('foo2_' + ctx.idx));
11291129
}
1130-
}, null, []);
1130+
}, null, null, ['tpl2', ''], templateRefExtractor);
11311131

1132-
template(5, null, null, [AttributeMarker.SelectOnly, 'vc']);
1132+
template(7, null, null, [AttributeMarker.SelectOnly, 'vc']);
11331133
}
11341134

11351135
if (rf & RenderFlags.Update) {
1136-
tpl1 = getOrCreateTemplateRef(getOrCreateNodeInjectorForNode(load(1)));
1137-
tpl2 = getOrCreateTemplateRef(getOrCreateNodeInjectorForNode(load(4)));
1136+
tpl1 = reference(2);
1137+
tpl2 = reference(6);
11381138
}
11391139

11401140
},
@@ -1219,14 +1219,14 @@ describe('query', () => {
12191219
if (rf & RenderFlags.Update) {
12201220
elementProperty(0, 'id', bind('foo_' + ctx.container_idx + '_' + ctx.idx));
12211221
}
1222-
}, null, []);
1222+
}, null, [], ['tpl', ''], templateRefExtractor);
12231223

1224-
template(2, null, null, [AttributeMarker.SelectOnly, 'vc']);
12251224
template(3, null, null, [AttributeMarker.SelectOnly, 'vc']);
1225+
template(4, null, null, [AttributeMarker.SelectOnly, 'vc']);
12261226
}
12271227

12281228
if (rf & RenderFlags.Update) {
1229-
tpl = getOrCreateTemplateRef(getOrCreateNodeInjectorForNode(load(1)));
1229+
tpl = reference(2);
12301230
}
12311231

12321232
},
@@ -1287,11 +1287,11 @@ describe('query', () => {
12871287
if (rf1 & RenderFlags.Create) {
12881288
element(0, 'span', ['id', 'from_tpl'], ['foo', '']);
12891289
}
1290-
}, undefined, undefined, ['tpl', '']);
1290+
}, undefined, undefined, ['tpl', ''], templateRefExtractor);
12911291
template(3, null, null, [AttributeMarker.SelectOnly, 'ngTemplateOutlet']);
12921292
}
12931293
if (rf & RenderFlags.Update) {
1294-
const tplRef = getOrCreateTemplateRef(getOrCreateNodeInjectorForNode(load(1)));
1294+
const tplRef = reference(2);
12951295
elementProperty(3, 'ngTemplateOutlet', bind(myApp.show ? tplRef : null));
12961296
}
12971297
},

0 commit comments

Comments
 (0)