From 5a86f7144f70bff90681739462821394b42ce3a4 Mon Sep 17 00:00:00 2001 From: Kara Erickson Date: Tue, 27 Mar 2018 11:01:52 -0700 Subject: [PATCH] fix(ivy): store local variables in data instead of calling loadDirective (#23029) PR Close #23029 --- packages/core/src/render3/instructions.ts | 141 ++++++++------- .../compiler_canonical/elements_spec.ts | 54 ++++++ packages/core/test/render3/di_spec.ts | 165 +++++++++++------- packages/core/test/render3/exports_spec.ts | 78 +++++---- packages/core/test/render3/properties_spec.ts | 10 +- packages/core/test/render3/query_spec.ts | 20 +-- 6 files changed, 292 insertions(+), 176 deletions(-) diff --git a/packages/core/src/render3/instructions.ts b/packages/core/src/render3/instructions.ts index 220717ac6a..2fd0c7e8c1 100644 --- a/packages/core/src/render3/instructions.ts +++ b/packages/core/src/render3/instructions.ts @@ -494,37 +494,27 @@ export function renderComponentOrTemplate( * ['id', 'warning5', 'class', 'alert'] */ export function elementStart( - index: number, name?: string, attrs?: string[] | null, localRefs?: string[] | null): RElement { + index: number, name: string, attrs?: string[] | null, localRefs?: string[] | null): RElement { let node: LElementNode; let native: RElement; + ngDevMode && + assertNull(currentView.bindingStartIndex, 'elements should be created before any bindings'); - if (name == null) { - // native node retrieval - used for exporting elements as tpl local variables (
) - const node = data[index] !; - native = node && (node as LElementNode).native; - } else { - ngDevMode && - assertNull(currentView.bindingStartIndex, 'elements should be created before any bindings'); + native = renderer.createElement(name); + node = createLNode(index, LNodeType.Element, native !, null); - native = renderer.createElement(name); - node = createLNode(index, LNodeType.Element, native !, null); + if (attrs) setUpAttributes(native, attrs); + appendChild(node.parent !, native, currentView); - if (attrs) setUpAttributes(native, attrs); - appendChild(node.parent !, native, currentView); + if (firstTemplatePass) { + const tNode = createTNode(name, attrs || null, null); + cacheMatchingDirectivesForNode(tNode); - if (firstTemplatePass) { - const tNode = createTNode(name, attrs || null, null, null); - cacheMatchingDirectivesForNode(tNode); - - ngDevMode && assertDataInRange(index - 1); - node.tNode = tData[index] = tNode; - - if (!isComponent(tNode)) { - tNode.localNames = findMatchingLocalNames(null, localRefs, -1, ''); - } - } - hack_declareDirectives(index, localRefs); + ngDevMode && assertDataInRange(index - 1); + node.tNode = tData[index] = tNode; } + + hack_declareDirectives(index, localRefs || null); return native; } @@ -590,11 +580,14 @@ export function isComponent(tNode: TNode): boolean { * This function instantiates the given directives. It is a hack since it assumes the directives * come in the correct order for DI. */ -function hack_declareDirectives(elementIndex: number, localRefs: string[] | null | undefined) { - const size = (previousOrParentNode.tNode !.flags & TNodeFlags.SIZE_MASK) >> TNodeFlags.SIZE_SHIFT; +function hack_declareDirectives(elementIndex: number, localRefs: string[] | null) { + const tNode = previousOrParentNode.tNode !; + const size = (tNode.flags & TNodeFlags.SIZE_MASK) >> TNodeFlags.SIZE_SHIFT; + + const exportsMap: {[key: string]: number}|null = firstTemplatePass && localRefs ? {'': -1} : null; if (size > 0) { - let startIndex = previousOrParentNode.tNode !.flags >> TNodeFlags.INDX_SHIFT; + let startIndex = tNode.flags >> TNodeFlags.INDX_SHIFT; const endIndex = startIndex + size; const tDirectives = currentView.tView.directives !; @@ -602,32 +595,59 @@ function hack_declareDirectives(elementIndex: number, localRefs: string[] | null // is not guaranteed. Must be refactored to take it into account. for (let i = startIndex; i < endIndex; i++) { const def = tDirectives[i] as DirectiveDef; - directiveCreate(elementIndex, def.factory(), def, localRefs); + directiveCreate(elementIndex, def.factory(), def); + saveNameToExportMap(startIndex, def, exportsMap); startIndex++; } } + + if (firstTemplatePass) cacheMatchingLocalNames(tNode, localRefs, exportsMap !); + saveResolvedLocalsInData(); +} + +/** Caches local names and their matching directive indices for query and template lookups. */ +function cacheMatchingLocalNames( + tNode: TNode, localRefs: string[] | null, exportsMap: {[key: string]: number}): void { + if (localRefs) { + const localNames: (string | number)[] = tNode.localNames = []; + + // Local names must be stored in tNode in the same order that localRefs are defined + // in the template to ensure the data is loaded in the same slots as their refs + // in the template (for template queries). + for (let i = 0; i < localRefs.length; i += 2) { + const index = exportsMap[localRefs[i | 1]]; + if (index == null) throw new Error(`Export of name '${localRefs[i | 1]}' not found!`); + localNames.push(localRefs[i], index); + } + } } /** - * Finds any local names that match the given directive's exportAs and returns them with directive - * index. If the directiveDef is null, it matches against the default '' value instead of - * exportAs. + * Builds up an export map as directives are created, so local refs can be quickly mapped + * to their directive instances. */ -function findMatchingLocalNames( - directiveDef: DirectiveDef| null, localRefs: string[] | null | undefined, index: number, - defaultExport?: string): (string | number)[]|null { - const exportAs = directiveDef && directiveDef.exportAs || defaultExport; - let matches: (string | number)[]|null = null; - if (exportAs != null && localRefs) { - for (let i = 0; i < localRefs.length; i = i + 2) { - const local = localRefs[i]; - const toExportAs = localRefs[i | 1]; - if (toExportAs === exportAs || toExportAs === defaultExport) { - (matches || (matches = [])).push(local, index); - } +function saveNameToExportMap( + index: number, def: DirectiveDef| ComponentDef, + exportsMap: {[key: string]: number} | null) { + if (exportsMap) { + if (def.exportAs) exportsMap[def.exportAs] = index; + if ((def as ComponentDef).template) exportsMap[''] = index; + } +} + +/** + * Takes a list of local names and indices and pushes the resolved local variable values + * to data[] in the same order as they are loaded in the template with load(). + */ +function saveResolvedLocalsInData(): void { + const localNames = previousOrParentNode.tNode !.localNames; + if (localNames) { + for (let i = 0; i < localNames.length; i += 2) { + const index = localNames[i | 1] as number; + const value = index === -1 ? previousOrParentNode.native : directives ![index]; + data.push(value); } } - return matches; } /** @@ -724,7 +744,7 @@ export function hostElement( def.onPush ? LViewFlags.Dirty : LViewFlags.CheckAlways)); if (firstTemplatePass) { - node.tNode = createTNode(tag as string, null, null, null); + node.tNode = createTNode(tag as string, null, null); // Root directive is stored at index 0, size 1 buildTNodeFlags(node.tNode, 0, 1, TNodeFlags.Component); currentView.tView.directives = [def]; @@ -879,13 +899,12 @@ export function elementProperty( * @returns the TNode object */ function createTNode( - tagName: string | null, attrs: string[] | null, data: TContainer | null, - localNames: (string | number)[] | null): TNode { + tagName: string | null, attrs: string[] | null, data: TContainer | null): TNode { return { flags: 0, tagName: tagName, attrs: attrs, - localNames: localNames, + localNames: null, initialInputs: undefined, inputs: undefined, outputs: undefined, @@ -1122,8 +1141,7 @@ export function textBinding(index: number, value: T | NO_CHANGE): void { * @param localRefs Names under which a query can retrieve the directive instance */ export function directiveCreate( - elementIndex: number, directive: T, directiveDef: DirectiveDef| ComponentDef, - localRefs?: string[] | null): T { + elementIndex: number, directive: T, directiveDef: DirectiveDef| ComponentDef): T { const index = directives ? directives.length : 0; const instance = baseDirectiveCreate(index, directive, directiveDef); @@ -1141,13 +1159,6 @@ export function directiveCreate( queueInitHooks(index, directiveDef.onInit, directiveDef.doCheck, currentView.tView); if (directiveDef.hostBindings) queueHostBindingForCheck(index, elementIndex); - - if (localRefs) { - const localNames = - findMatchingLocalNames(directiveDef, localRefs, index, isComponent ? '' : undefined); - tNode.localNames = - localNames && tNode.localNames ? tNode.localNames.concat(localNames) : localNames; - } } if (tNode && tNode.attrs) { @@ -1172,9 +1183,7 @@ function addComponentLogic( initChangeDetectorIfExisting(previousOrParentNode.nodeInjector, instance, hostView); - if (firstTemplatePass) { - queueComponentIndexForCheck(index, elementIndex); - } + if (firstTemplatePass) queueComponentIndexForCheck(index, elementIndex); } /** @@ -1308,8 +1317,7 @@ export function container( const node = createLNode(index, LNodeType.Container, undefined, lContainer); if (node.tNode == null) { - const localNames: (string | number)[]|null = findMatchingLocalNames(null, localRefs, -1, ''); - node.tNode = tData[index] = createTNode(tagName || null, attrs || null, [], localNames); + node.tNode = tData[index] = createTNode(tagName || null, attrs || null, []); } // Containers are added to the current view tree instead of their embedded views @@ -1317,7 +1325,9 @@ export function container( addToViewTree(node.data); if (firstTemplatePass) cacheMatchingDirectivesForNode(node.tNode); - hack_declareDirectives(index, localRefs); + + // TODO: handle TemplateRef! + hack_declareDirectives(index, localRefs || null); isParent = false; ngDevMode && assertNodeType(previousOrParentNode, LNodeType.Container); @@ -1616,7 +1626,7 @@ export function projection( const node = createLNode(nodeIndex, LNodeType.Projection, null, {head: null, tail: null}); if (node.tNode == null) { - node.tNode = createTNode(null, attrs || null, null, null); + node.tNode = createTNode(null, attrs || null, null); } isParent = false; // self closing @@ -2167,7 +2177,8 @@ function assertDataInRange(index: number, arr?: any[]) { function assertDataNext(index: number, arr?: any[]) { if (arr == null) arr = data; - assertEqual(arr.length, index, 'index expected to be at the end of arr'); + assertEqual( + arr.length, index, `index ${index} expected to be at the end of arr (length ${arr.length})`); } export function _getComponentHostLElementNode(component: T): LElementNode { diff --git a/packages/core/test/render3/compiler_canonical/elements_spec.ts b/packages/core/test/render3/compiler_canonical/elements_spec.ts index 1457764deb..090640b694 100644 --- a/packages/core/test/render3/compiler_canonical/elements_spec.ts +++ b/packages/core/test/render3/compiler_canonical/elements_spec.ts @@ -53,6 +53,60 @@ describe('elements', () => { .toEqual('
Hello World!
'); }); + it('should support local refs', () => { + type $LocalRefComp$ = LocalRefComp; + + class Dir { + value = 'one'; + + static ngDirectiveDef = $r3$.ɵdefineDirective({ + type: Dir, + selector: [[['', 'dir', ''], null]], + factory: function DirA_Factory() { return new Dir(); }, + exportAs: 'dir' + }); + } + + // NORMATIVE + const $e0_attrs$ = ['dir', '']; + const $e0_locals$ = ['dir', 'dir', 'foo', '']; + // /NORMATIVE + + @Component({ + selector: 'local-ref-comp', + template: ` +
+ {{ dir.value }} - {{ foo.tagName }} + ` + }) + class LocalRefComp { + // NORMATIVE + static ngComponentDef = $r3$.ɵdefineComponent({ + type: LocalRefComp, + selector: [[['local-ref-comp'], null]], + factory: function LocalRefComp_Factory() { return new LocalRefComp(); }, + template: function LocalRefComp_Template(ctx: $LocalRefComp$, cm: $boolean$) { + if (cm) { + $r3$.ɵE(0, 'div', $e0_attrs$, $e0_locals$); + $r3$.ɵe(); + $r3$.ɵT(3); + } + const $tmp$ = $r3$.ɵld(1) as any; + const $tmp_2$ = $r3$.ɵld(2) as any; + $r3$.ɵt(3, $r3$.ɵi2(' ', $tmp$.value, ' - ', $tmp_2$.tagName, '')); + } + }); + // /NORMATIVE + } + + // NON-NORMATIVE + LocalRefComp.ngComponentDef.directiveDefs = () => [Dir.ngDirectiveDef]; + // /NON-NORMATIVE + + const fixture = new ComponentFixture(LocalRefComp); + expect(fixture.html).toEqual(`
one - DIV`); + }); + it('should support listeners', () => { type $ListenerComp$ = ListenerComp; diff --git a/packages/core/test/render3/di_spec.ts b/packages/core/test/render3/di_spec.ts index 589d210611..1b3c188360 100644 --- a/packages/core/test/render3/di_spec.ts +++ b/packages/core/test/render3/di_spec.ts @@ -11,7 +11,7 @@ import {ChangeDetectorRef, ElementRef, TemplateRef, ViewContainerRef} from '@ang import {defineComponent} from '../../src/render3/definition'; import {InjectFlags, bloomAdd, bloomFindPossibleInjector, getOrCreateNodeInjector, injectAttribute} from '../../src/render3/di'; import {NgOnChangesFeature, PublicFeature, defineDirective, directiveInject, injectChangeDetectorRef, injectElementRef, injectTemplateRef, injectViewContainerRef} from '../../src/render3/index'; -import {bind, container, containerRefreshEnd, containerRefreshStart, createLNode, createLView, createTView, elementEnd, elementStart, embeddedViewEnd, embeddedViewStart, enterView, interpolation2, leaveView, load, loadDirective, projection, projectionDef, text, textBinding} from '../../src/render3/instructions'; +import {bind, container, containerRefreshEnd, containerRefreshStart, createLNode, createLView, createTView, elementEnd, elementStart, embeddedViewEnd, embeddedViewStart, enterView, interpolation2, leaveView, load, projection, projectionDef, text, textBinding} from '../../src/render3/instructions'; import {LInjector} from '../../src/render3/interfaces/injector'; import {LNodeType} from '../../src/render3/interfaces/node'; import {LViewFlags} from '../../src/render3/interfaces/view'; @@ -24,18 +24,23 @@ describe('di', () => { it('should create directive with no deps', () => { class Directive { value: string = 'Created'; - static ngDirectiveDef = defineDirective( - {type: Directive, selector: [[['', 'dir', ''], null]], factory: () => new Directive}); + static ngDirectiveDef = defineDirective({ + type: Directive, + selector: [[['', 'dir', ''], null]], + factory: () => new Directive, + exportAs: 'dir' + }); } + /**
{{ dir.value }}
*/ function Template(ctx: any, cm: boolean) { if (cm) { - elementStart(0, 'div', ['dir', '']); - { text(1); } + elementStart(0, 'div', ['dir', ''], ['dir', 'dir']); + { text(2); } elementEnd(); } - // TODO: remove loadDirective when removing directive references - textBinding(1, bind(loadDirective(0).value)); + const tmp = load(1) as any; + textBinding(2, bind(tmp.value)); } expect(renderToHtml(Template, {}, [Directive.ngDirectiveDef])) @@ -71,22 +76,28 @@ describe('di', () => { static ngDirectiveDef = defineDirective({ type: DirectiveC, selector: [[['', 'dirC', ''], null]], - factory: () => new DirectiveC(directiveInject(DirectiveA), directiveInject(DirectiveB)) + factory: () => new DirectiveC(directiveInject(DirectiveA), directiveInject(DirectiveB)), + exportAs: 'dirC' }); } + /** + *
+ * {{ dir.value }} + *
+ */ function Template(ctx: any, cm: boolean) { if (cm) { elementStart(0, 'div', ['dirA', '']); { - elementStart(1, 'span', ['dirB', '', 'dirC', '']); - { text(2); } + elementStart(1, 'span', ['dirB', '', 'dirC', ''], ['dir', 'dirC']); + { text(3); } elementEnd(); } elementEnd(); } - // TODO: remove loadDirective when removing directive references - textBinding(2, bind(loadDirective(2).value)); + const tmp = load(2) as any; + textBinding(3, bind(tmp.value)); } const defs = @@ -107,7 +118,8 @@ describe('di', () => { type: Directive, selector: [[['', 'dir', ''], null]], factory: () => new Directive(injectElementRef()), - features: [PublicFeature] + features: [PublicFeature], + exportAs: 'dir' }); } @@ -119,21 +131,26 @@ describe('di', () => { static ngDirectiveDef = defineDirective({ type: DirectiveSameInstance, selector: [[['', 'dirSame', ''], null]], - factory: () => new DirectiveSameInstance(injectElementRef(), directiveInject(Directive)) + factory: () => new DirectiveSameInstance(injectElementRef(), directiveInject(Directive)), + exportAs: 'dirSame' }); } + /** + *
+ * {{ dir.value }} - {{ dirSame.value }} + *
+ */ function Template(ctx: any, cm: boolean) { if (cm) { - elementStart(0, 'div', ['dir', '', 'dirSame', '']); - { text(1); } + elementStart(0, 'div', ['dir', '', 'dirSame', ''], ['dirSame', 'dirSame', 'dir', 'dir']); + { text(3); } elementEnd(); } - // TODO: remove loadDirective when removing directive references - textBinding( - 1, interpolation2( - '', loadDirective(0).value, '-', - loadDirective(1).value, '')); + + const tmp1 = load(1) as any; + const tmp2 = load(2) as any; + textBinding(3, interpolation2('', tmp2.value, '-', tmp1.value, '')); } const defs = [Directive.ngDirectiveDef, DirectiveSameInstance.ngDirectiveDef]; @@ -153,7 +170,8 @@ describe('di', () => { type: Directive, selector: [[['', 'dir', ''], null]], factory: () => new Directive(injectTemplateRef()), - features: [PublicFeature] + features: [PublicFeature], + exportAs: 'dir' }); } @@ -165,21 +183,25 @@ describe('di', () => { static ngDirectiveDef = defineDirective({ type: DirectiveSameInstance, selector: [[['', 'dirSame', ''], null]], - factory: () => new DirectiveSameInstance(injectTemplateRef(), directiveInject(Directive)) + factory: () => new DirectiveSameInstance(injectTemplateRef(), directiveInject(Directive)), + exportAs: 'dirSame' }); } - + /** + * + * {{ dir.value }} - {{ dirSame.value }} + * + */ function Template(ctx: any, cm: any) { if (cm) { - container(0, function() {}, undefined, ['dir', '', 'dirSame', '']); - text(1); + container(0, function() { + }, undefined, ['dir', '', 'dirSame', ''], ['dir', 'dir', 'dirSame', 'dirSame']); + text(3); } - // TODO: remove loadDirective when removing directive references - textBinding( - 1, interpolation2( - '', loadDirective(0).value, '-', - loadDirective(1).value, '')); + const tmp1 = load(1) as any; + const tmp2 = load(2) as any; + textBinding(3, interpolation2('', tmp1.value, '-', tmp2.value, '')); } const defs = [Directive.ngDirectiveDef, DirectiveSameInstance.ngDirectiveDef]; @@ -198,7 +220,8 @@ describe('di', () => { type: Directive, selector: [[['', 'dir', ''], null]], factory: () => new Directive(injectViewContainerRef()), - features: [PublicFeature] + features: [PublicFeature], + exportAs: 'dir' }); } @@ -211,21 +234,26 @@ describe('di', () => { type: DirectiveSameInstance, selector: [[['', 'dirSame', ''], null]], factory: - () => new DirectiveSameInstance(injectViewContainerRef(), directiveInject(Directive)) + () => new DirectiveSameInstance(injectViewContainerRef(), directiveInject(Directive)), + exportAs: 'dirSame' }); } + /** + *
+ * {{ dir.value }} - {{ dirSame.value }} + *
+ */ function Template(ctx: any, cm: boolean) { if (cm) { - elementStart(0, 'div', ['dir', '', 'dirSame', '']); - { text(1); } + elementStart(0, 'div', ['dir', '', 'dirSame', ''], ['dir', 'dir', 'dirSame', 'dirSame']); + { text(3); } elementEnd(); } - // TODO: remove loadDirective when removing directive references - textBinding( - 1, interpolation2( - '', loadDirective(0).value, '-', - loadDirective(1).value, '')); + + const tmp1 = load(1) as any; + const tmp2 = load(2) as any; + textBinding(3, interpolation2('', tmp1.value, '-', tmp2.value, '')); } const defs = [Directive.ngDirectiveDef, DirectiveSameInstance.ngDirectiveDef]; @@ -310,10 +338,10 @@ describe('di', () => { if (cm) { elementStart(0, 'my-comp', ['dir', '', 'dirSame', ''], ['dir', 'dir']); elementEnd(); - text(1); + text(2); } - // TODO: remove loadDirective when removing directive references - textBinding(1, bind(loadDirective(1).value)); + const tmp = load(1) as any; + textBinding(2, bind(tmp.value)); }, defs); const app = renderComponent(MyApp); @@ -338,11 +366,11 @@ describe('di', () => { template: function(ctx: any, cm: boolean) { if (cm) { elementStart(0, 'div', ['dir', '', 'dirSame', ''], ['dir', 'dir']); - { text(1); } + { text(2); } elementEnd(); } - // TODO: remove loadDirective when removing directive references - textBinding(1, bind(loadDirective(0).value)); + const tmp = load(1) as any; + textBinding(2, bind(tmp.value)); }, directiveDefs: defs }); @@ -378,10 +406,10 @@ describe('di', () => { elementEnd(); } elementEnd(); - text(2); + text(3); } - // TODO: remove loadDirective when removing directive references - textBinding(2, bind(loadDirective(1).value)); + const tmp = load(2) as any; + textBinding(3, bind(tmp.value)); }, directiveDefs: defs }); @@ -420,11 +448,11 @@ describe('di', () => { if (ctx.showing) { if (embeddedViewStart(0)) { elementStart(0, 'div', ['dir', '', 'dirSame', ''], ['dir', 'dir']); - { text(1); } + { text(2); } elementEnd(); } - // TODO: remove loadDirective when removing directive references - textBinding(1, bind(loadDirective(0).value)); + const tmp = load(1) as any; + textBinding(2, bind(tmp.value)); } embeddedViewEnd(); } @@ -463,11 +491,11 @@ describe('di', () => { function C1(ctx1: any, cm1: boolean) { if (cm1) { elementStart(0, 'div', ['dir', '', 'dirSame', ''], ['dir', 'dir']); - { text(1); } + { text(2); } elementEnd(); } - // TODO: remove loadDirective when removing directive references - textBinding(1, bind(loadDirective(0).value)); + const tmp = load(1) as any; + textBinding(2, bind(tmp.value)); } }, directiveDefs: defs @@ -600,7 +628,8 @@ describe('di', () => { type: ChildDirective, selector: [[['', 'childDir', ''], null]], factory: () => new ChildDirective(directiveInject(ParentDirective)), - features: [PublicFeature] + features: [PublicFeature], + exportAs: 'childDir' }); } @@ -611,10 +640,18 @@ describe('di', () => { selector: [[['', 'child2Dir', ''], null]], type: Child2Directive, factory: () => new Child2Directive( - directiveInject(ParentDirective), directiveInject(ChildDirective)) + directiveInject(ParentDirective), directiveInject(ChildDirective)), + exportAs: 'child2Dir' }); } + /** + *
+ * + * {{ child1.value }} - {{ child2.value }} + * + *
+ */ function Template(ctx: any, cm: boolean) { if (cm) { elementStart(0, 'div', ['parentDir', '']); @@ -624,15 +661,15 @@ describe('di', () => { containerRefreshStart(1); { if (embeddedViewStart(0)) { - elementStart(0, 'span', ['childDir', '', 'child2Dir', '']); - { text(1); } + elementStart( + 0, 'span', ['childDir', '', 'child2Dir', ''], + ['child1', 'childDir', 'child2', 'child2Dir']); + { text(3); } elementEnd(); } - // TODO: remove loadDirective when removing directive references - textBinding( - 1, interpolation2( - '', loadDirective(0).value, '-', - loadDirective(1).value, '')); + const tmp1 = load(1) as any; + const tmp2 = load(2) as any; + textBinding(3, interpolation2('', tmp1.value, '-', tmp2.value, '')); embeddedViewEnd(); } containerRefreshEnd(); diff --git a/packages/core/test/render3/exports_spec.ts b/packages/core/test/render3/exports_spec.ts index 821a6724e2..2b21ea70b3 100644 --- a/packages/core/test/render3/exports_spec.ts +++ b/packages/core/test/render3/exports_spec.ts @@ -7,9 +7,9 @@ */ import {defineComponent, defineDirective} from '../../src/render3/index'; -import {bind, container, containerRefreshEnd, containerRefreshStart, elementAttribute, elementClassNamed, elementEnd, elementProperty, elementStart, embeddedViewEnd, embeddedViewStart, load, loadDirective, text, textBinding} from '../../src/render3/instructions'; +import {bind, container, containerRefreshEnd, containerRefreshStart, elementAttribute, elementClassNamed, elementEnd, elementProperty, elementStart, embeddedViewEnd, embeddedViewStart, load, text, textBinding} from '../../src/render3/instructions'; -import {renderToHtml} from './render_util'; +import {ComponentFixture, createComponent, renderToHtml} from './render_util'; describe('exports', () => { it('should support export of DOM element', () => { @@ -19,10 +19,10 @@ describe('exports', () => { if (cm) { elementStart(0, 'input', ['value', 'one'], ['myInput', '']); elementEnd(); - text(1); + text(2); } - let myInput = elementStart(0); - textBinding(1, (myInput as any).value); + const tmp = load(1) as any; + textBinding(2, tmp.value); } expect(renderToHtml(Template, {})).toEqual('one'); @@ -35,10 +35,10 @@ describe('exports', () => { if (cm) { elementStart(0, 'comp', null, ['myComp', '']); elementEnd(); - text(1); + text(2); } - // TODO: replace loadDirective when removing directive refs - textBinding(1, loadDirective(0).name); + const tmp = load(1) as any; + textBinding(2, tmp.name); } class MyComponent { @@ -87,11 +87,11 @@ describe('exports', () => { if (cm) { elementStart(0, 'comp', null, ['myComp', '']); elementEnd(); - elementStart(1, 'div', ['myDir', '']); + elementStart(2, 'div', ['myDir', '']); elementEnd(); } - // TODO: replace loadDirective when removing directive refs - elementProperty(1, 'myDir', bind(loadDirective(0))); + const tmp = load(1) as any; + elementProperty(2, 'myDir', bind(tmp)); } renderToHtml(Template, {}, defs); @@ -105,10 +105,10 @@ describe('exports', () => { if (cm) { elementStart(0, 'div', ['someDir', ''], ['myDir', 'someDir']); elementEnd(); - text(1); + text(2); } - // TODO: replace loadDirective when removing directive refs - textBinding(1, loadDirective(0).name); + const tmp = load(1) as any; + textBinding(2, tmp.name); } class SomeDir { @@ -125,6 +125,21 @@ describe('exports', () => { .toEqual('
Drew'); }); + it('should throw if export name is not found', () => { + + /**
*/ + const App = createComponent('app', function(ctx: any, cm: boolean) { + if (cm) { + elementStart(0, 'div', null, ['myDir', 'someDir']); + elementEnd(); + } + }); + + expect(() => { + const fixture = new ComponentFixture(App); + }).toThrowError(/Export of name 'someDir' not found!/); + }); + describe('forward refs', () => { it('should work with basic text bindings', () => { /** {{ myInput.value}} */ @@ -134,8 +149,8 @@ describe('exports', () => { elementStart(1, 'input', ['value', 'one'], ['myInput', '']); elementEnd(); } - let myInput = elementStart(1); - textBinding(0, bind((myInput as any).value)); + const tmp = load(2) as any; + textBinding(0, bind(tmp.value)); } expect(renderToHtml(Template, {})).toEqual('one'); @@ -151,8 +166,8 @@ describe('exports', () => { elementStart(1, 'input', ['value', 'one'], ['myInput', '']); elementEnd(); } - let myInput = elementStart(1); - elementProperty(0, 'title', bind(myInput && (myInput as any).value)); + const tmp = load(2) as any; + elementProperty(0, 'title', bind(tmp.value)); } expect(renderToHtml(Template, {})).toEqual('
'); @@ -167,8 +182,8 @@ describe('exports', () => { elementStart(1, 'input', ['value', 'one'], ['myInput', '']); elementEnd(); } - let myInput = elementStart(1); - elementAttribute(0, 'aria-label', bind(myInput && (myInput as any).value)); + const tmp = load(2) as any; + elementAttribute(0, 'aria-label', bind(tmp.value)); } expect(renderToHtml(Template, {})).toEqual('
'); @@ -183,8 +198,8 @@ describe('exports', () => { elementStart(1, 'input', ['type', 'checkbox', 'checked', 'true'], ['myInput', '']); elementEnd(); } - let myInput = elementStart(1); - elementClassNamed(0, 'red', bind(myInput && (myInput as any).checked)); + const tmp = load(2) as any; + elementClassNamed(0, 'red', bind(tmp.checked)); } expect(renderToHtml(Template, {})) @@ -228,8 +243,8 @@ describe('exports', () => { elementStart(1, 'comp', null, ['myComp', '']); elementEnd(); } - // TODO: replace loadDirective when removing directive refs - elementProperty(0, 'myDir', bind(loadDirective(1))); + const tmp = load(2) as any; + elementProperty(0, 'myDir', bind(tmp)); } renderToHtml(Template, {}, [MyComponent.ngComponentDef, MyDir.ngDirectiveDef]); @@ -245,14 +260,13 @@ describe('exports', () => { text(1); elementStart(2, 'comp', null, ['myComp', '']); elementEnd(); - elementStart(3, 'input', ['value', 'one'], ['myInput', '']); + elementStart(4, 'input', ['value', 'one'], ['myInput', '']); elementEnd(); } - let myInput = elementStart(3); - // TODO: replace loadDirective when removing directive refs - let myComp = loadDirective(0); - textBinding(0, bind(myInput && (myInput as any).value)); - textBinding(1, bind(myComp && myComp.name)); + const tmp1 = load(3) as any; + const tmp2 = load(5) as any; + textBinding(0, bind(tmp2.value)); + textBinding(1, bind(tmp1.name)); } let myComponent: MyComponent; @@ -290,8 +304,8 @@ describe('exports', () => { elementStart(1, 'input', ['value', 'one'], ['myInput', '']); elementEnd(); } - let myInput = elementStart(1); - textBinding(0, bind(myInput && (myInput as any).value)); + const tmp = load(2) as any; + textBinding(0, bind(tmp.value)); } embeddedViewEnd(); } diff --git a/packages/core/test/render3/properties_spec.ts b/packages/core/test/render3/properties_spec.ts index fb78c8b622..9b9197e887 100644 --- a/packages/core/test/render3/properties_spec.ts +++ b/packages/core/test/render3/properties_spec.ts @@ -9,9 +9,9 @@ import {EventEmitter} from '@angular/core'; import {defineComponent, defineDirective, tick} from '../../src/render3/index'; -import {NO_CHANGE, bind, container, containerRefreshEnd, containerRefreshStart, elementEnd, elementProperty, elementStart, embeddedViewEnd, embeddedViewStart, interpolation1, listener, loadDirective, text, textBinding} from '../../src/render3/instructions'; +import {NO_CHANGE, bind, container, containerRefreshEnd, containerRefreshStart, elementEnd, elementProperty, elementStart, embeddedViewEnd, embeddedViewStart, interpolation1, listener, load, loadDirective, text, textBinding} from '../../src/render3/instructions'; -import {ComponentFixture, TemplateFixture, createComponent, renderToHtml} from './render_util'; +import {ComponentFixture, renderToHtml} from './render_util'; describe('elementProperty', () => { @@ -533,10 +533,10 @@ describe('elementProperty', () => { if (cm) { elementStart(0, 'div', ['role', 'button', 'myDir', ''], ['dir', 'myDir']); elementEnd(); - text(1); + text(2); } - // TODO: remove this loadDirective when removing MyDir - textBinding(1, bind(loadDirective(0).role)); + const tmp = load(1) as any; + textBinding(2, bind(tmp.role)); }, factory: () => new Comp(), directiveDefs: () => [MyDir.ngDirectiveDef] diff --git a/packages/core/test/render3/query_spec.ts b/packages/core/test/render3/query_spec.ts index e5a464415d..b9a458281e 100644 --- a/packages/core/test/render3/query_spec.ts +++ b/packages/core/test/render3/query_spec.ts @@ -179,7 +179,7 @@ describe('query', () => { query(0, ['foo'], false, QUERY_READ_FROM_NODE); elToQuery = elementStart(1, 'div', null, ['foo', '']); elementEnd(); - elementStart(2, 'div'); + elementStart(3, 'div'); elementEnd(); } queryRefresh(tmp = load>(0)) && (ctx.query = tmp as QueryList); @@ -209,7 +209,7 @@ describe('query', () => { query(1, ['bar'], false, QUERY_READ_FROM_NODE); elToQuery = elementStart(2, 'div', null, ['foo', '', 'bar', '']); elementEnd(); - elementStart(3, 'div'); + elementStart(5, 'div'); elementEnd(); } queryRefresh(tmp = load>(0)) && (ctx.fooQuery = tmp as QueryList); @@ -245,9 +245,9 @@ describe('query', () => { query(0, ['foo', 'bar'], undefined, QUERY_READ_FROM_NODE); el1ToQuery = elementStart(1, 'div', null, ['foo', '']); elementEnd(); - elementStart(2, 'div'); + elementStart(3, 'div'); elementEnd(); - el2ToQuery = elementStart(3, 'div', null, ['bar', '']); + el2ToQuery = elementStart(4, 'div', null, ['bar', '']); elementEnd(); } queryRefresh(tmp = load>(0)) && (ctx.query = tmp as QueryList); @@ -276,7 +276,7 @@ describe('query', () => { query(0, ['foo'], false, QUERY_READ_ELEMENT_REF); elToQuery = elementStart(1, 'div', null, ['foo', '']); elementEnd(); - elementStart(2, 'div'); + elementStart(3, 'div'); elementEnd(); } queryRefresh(tmp = load>(0)) && (ctx.query = tmp as QueryList); @@ -708,11 +708,11 @@ describe('query', () => { query(0, ['foo'], true, QUERY_READ_FROM_NODE); firstEl = elementStart(1, 'span', null, ['foo', '']); elementEnd(); - container(2); - lastEl = elementStart(3, 'span', null, ['foo', '']); + container(3); + lastEl = elementStart(4, 'span', null, ['foo', '']); elementEnd(); } - containerRefreshStart(2); + containerRefreshStart(3); { if (ctx.exp) { let cm1 = embeddedViewStart(1); @@ -838,9 +838,9 @@ describe('query', () => { if (cm1) { firstEl = elementStart(0, 'div', null, ['foo', '']); elementEnd(); - container(1); + container(2); } - containerRefreshStart(1); + containerRefreshStart(2); { if (ctx.exp2) { let cm2 = embeddedViewStart(0);