fix(ivy): store local variables in data instead of calling loadDirective (#23029)

PR Close #23029
This commit is contained in:
Kara Erickson 2018-03-27 11:01:52 -07:00 committed by Alex Rickabaugh
parent bd024c02e2
commit 5a86f7144f
6 changed files with 292 additions and 176 deletions

View File

@ -494,37 +494,27 @@ export function renderComponentOrTemplate<T>(
* ['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 (<div #foo>)
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<any>;
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<any>| 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<any>| ComponentDef<any>,
exportsMap: {[key: string]: number} | null) {
if (exportsMap) {
if (def.exportAs) exportsMap[def.exportAs] = index;
if ((def as ComponentDef<any>).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<T>(
* @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<T>(index: number, value: T | NO_CHANGE): void {
* @param localRefs Names under which a query can retrieve the directive instance
*/
export function directiveCreate<T>(
elementIndex: number, directive: T, directiveDef: DirectiveDef<T>| ComponentDef<T>,
localRefs?: string[] | null): T {
elementIndex: number, directive: T, directiveDef: DirectiveDef<T>| ComponentDef<T>): T {
const index = directives ? directives.length : 0;
const instance = baseDirectiveCreate(index, directive, directiveDef);
@ -1141,13 +1159,6 @@ export function directiveCreate<T>(
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<T>(
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<T>(component: T): LElementNode {

View File

@ -53,6 +53,60 @@ describe('elements', () => {
.toEqual('<div class="my-app" title="Hello">Hello <b>World</b>!</div>');
});
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: `
<div dir #dir="dir" #foo></div>
{{ 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(`<div dir=""></div> one - DIV`);
});
it('should support listeners', () => {
type $ListenerComp$ = ListenerComp;

View File

@ -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'
});
}
/** <div dir #dir="dir"> {{ dir.value }} </div> */
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<Directive>(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'
});
}
/**
* <div dirA>
* <span dirB dirC #dir="dirC"> {{ dir.value }} </span>
* </div>
*/
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<DirectiveC>(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'
});
}
/**
* <div dir dirSame #dirSame="dirSame" #dir="dir">
* {{ dir.value }} - {{ dirSame.value }}
* </div>
*/
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<Directive>(0).value, '-',
loadDirective<DirectiveSameInstance>(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'
});
}
/**
* <ng-template dir dirSame #dir="dir" #dirSame="dirSame">
* {{ dir.value }} - {{ dirSame.value }}
* </ng-template>
*/
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<Directive>(0).value, '-',
loadDirective<DirectiveSameInstance>(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'
});
}
/**
* <div dir dirSame #dir="dir" #dirSame="dirSame">
* {{ dir.value }} - {{ dirSame.value }}
* </div>
*/
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<Directive>(0).value, '-',
loadDirective<DirectiveSameInstance>(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<Directive>(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<Directive>(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<Directive>(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<Directive>(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<Directive>(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'
});
}
/**
* <div parentDir>
* <span childDir child2Dir #child1="childDir" #child2="child2Dir">
* {{ child1.value }} - {{ child2.value }}
* </span>
* </div>
*/
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<ChildDirective>(0).value, '-',
loadDirective<Child2Directive>(1).value, ''));
const tmp1 = load(1) as any;
const tmp2 = load(2) as any;
textBinding(3, interpolation2('', tmp1.value, '-', tmp2.value, ''));
embeddedViewEnd();
}
containerRefreshEnd();

View File

@ -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('<input value="one">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<MyComponent>(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<MyComponent>(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<SomeDir>(0).name);
const tmp = load(1) as any;
textBinding(2, tmp.name);
}
class SomeDir {
@ -125,6 +125,21 @@ describe('exports', () => {
.toEqual('<div somedir=""></div>Drew');
});
it('should throw if export name is not found', () => {
/** <div #myDir="someDir"></div> */
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}} <input value="one" #myInput> */
@ -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<input value="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('<div title="one"></div><input value="one">');
@ -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('<div aria-label="one"></div><input value="one">');
@ -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<MyComponent>(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<MyComponent>(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();
}

View File

@ -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<MyDir>(0).role));
const tmp = load(1) as any;
textBinding(2, bind(tmp.role));
},
factory: () => new Comp(),
directiveDefs: () => [MyDir.ngDirectiveDef]

View File

@ -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<QueryList<any>>(0)) && (ctx.query = tmp as QueryList<any>);
@ -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<QueryList<any>>(0)) && (ctx.fooQuery = tmp as QueryList<any>);
@ -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<QueryList<any>>(0)) && (ctx.query = tmp as QueryList<any>);
@ -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<QueryList<any>>(0)) && (ctx.query = tmp as QueryList<any>);
@ -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);