fix(ivy): store local variables in data instead of calling loadDirective (#23029)
PR Close #23029
This commit is contained in:
parent
bd024c02e2
commit
5a86f7144f
|
@ -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 {
|
||||
|
|
|
@ -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;
|
||||
|
||||
|
|
|
@ -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();
|
||||
|
|
|
@ -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();
|
||||
}
|
||||
|
|
|
@ -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]
|
||||
|
|
|
@ -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);
|
||||
|
|
Loading…
Reference in New Issue