refactor(core): store locals in main array in rederer3 (#20855)

PR Close #20855
This commit is contained in:
Miško Hevery
2017-12-08 11:48:54 -08:00
committed by Igor Minar
parent 0fa818b318
commit 93b00cceb6
14 changed files with 555 additions and 242 deletions

View File

@ -19,8 +19,8 @@ describe('content projection', () => {
const Child = createComponent('child', function(ctx: any, cm: boolean) {
if (cm) {
m(0, dP());
E(0, 'div');
{ P(1, 0); }
E(1, 'div');
{ P(2, 0); }
e();
}
});
@ -47,7 +47,7 @@ describe('content projection', () => {
const Child = createComponent('child', function(ctx: any, cm: boolean) {
if (cm) {
m(0, dP());
P(0, 0);
P(1, 0);
}
});
const Parent = createComponent('parent', function(ctx: any, cm: boolean) {
@ -69,21 +69,21 @@ describe('content projection', () => {
const GrandChild = createComponent('grand-child', function(ctx: any, cm: boolean) {
if (cm) {
m(0, dP());
E(0, 'div');
{ P(1, 0); }
E(1, 'div');
{ P(2, 0); }
e();
}
});
const Child = createComponent('child', function(ctx: any, cm: boolean) {
if (cm) {
m(0, dP());
E(0, GrandChild.ngComponentDef);
E(1, GrandChild.ngComponentDef);
{
D(0, GrandChild.ngComponentDef.n(), GrandChild.ngComponentDef);
P(1, 0);
P(2, 0);
}
e();
GrandChild.ngComponentDef.r(0, 0);
GrandChild.ngComponentDef.r(0, 1);
}
});
const Parent = createComponent('parent', function(ctx: any, cm: boolean) {
@ -109,8 +109,8 @@ describe('content projection', () => {
const Child = createComponent('child', function(ctx: any, cm: boolean) {
if (cm) {
m(0, dP());
E(0, 'div');
{ P(1, 0); }
E(1, 'div');
{ P(2, 0); }
e();
}
});
@ -148,12 +148,53 @@ describe('content projection', () => {
expect(toHtml(parent)).toEqual('<child><div>()</div></child>');
});
it('should project content with container into root', () => {
const Child = createComponent('child', function(ctx: any, cm: boolean) {
if (cm) {
m(0, dP());
P(1, 0);
}
});
const Parent = createComponent('parent', function(ctx: {value: any}, cm: boolean) {
if (cm) {
E(0, Child.ngComponentDef);
{
D(0, Child.ngComponentDef.n(), Child.ngComponentDef);
C(1);
c();
}
e();
}
rC(1);
{
if (ctx.value) {
if (V(0)) {
T(0, 'content');
}
v();
}
}
rc();
Child.ngComponentDef.r(0, 0);
});
const parent = renderComponent(Parent);
expect(toHtml(parent)).toEqual('<child></child>');
parent.value = true;
detectChanges(parent);
expect(toHtml(parent)).toEqual('<child>content</child>');
parent.value = false;
detectChanges(parent);
expect(toHtml(parent)).toEqual('<child></child>');
});
it('should project content with container and if-else.', () => {
const Child = createComponent('child', function(ctx: any, cm: boolean) {
if (cm) {
m(0, dP());
E(0, 'div');
{ P(1, 0); }
E(1, 'div');
{ P(2, 0); }
e();
}
});
@ -211,14 +252,14 @@ describe('content projection', () => {
const Child = createComponent('child', function(ctx: any, cm: boolean) {
if (cm) {
m(0, dP());
E(0, 'div');
E(1, 'div');
{
C(1);
C(2);
c();
}
e();
}
rC(1);
rC(2);
{
if (!ctx.skipContent) {
if (V(0)) {
@ -268,14 +309,14 @@ describe('content projection', () => {
const Child = createComponent('child', function(ctx: any, cm: boolean) {
if (cm) {
m(0, dP());
E(0, 'div');
E(1, 'div');
{
C(1);
C(2);
c();
}
e();
}
rC(1);
rC(2);
{
if (!ctx.skipContent) {
if (V(0)) {
@ -317,11 +358,11 @@ describe('content projection', () => {
const Child = createComponent('child', function(ctx: any, cm: boolean) {
if (cm) {
m(0, dP());
E(0, 'div');
{ P(1, 0); }
E(1, 'div');
{ P(2, 0); }
e();
E(2, 'span');
{ P(3, 0); }
E(3, 'span');
{ P(4, 0); }
e();
}
});
@ -366,15 +407,15 @@ describe('content projection', () => {
const Child = createComponent('child', function(ctx: any, cm: boolean) {
if (cm) {
m(0, dP());
P(0, 0);
E(1, 'div');
P(1, 0);
E(2, 'div');
{
C(2);
C(3);
c();
}
e();
}
rC(2);
rC(3);
{
if (ctx.show) {
if (V(0)) {
@ -419,11 +460,11 @@ describe('content projection', () => {
if (cm) {
m(0,
dP([[[['span', 'title', 'toFirst'], null]], [[['span', 'title', 'toSecond'], null]]]));
E(0, 'div', ['id', 'first']);
{ P(1, 0, 1); }
E(1, 'div', ['id', 'first']);
{ P(2, 0, 1); }
e();
E(2, 'div', ['id', 'second']);
{ P(3, 0, 2); }
E(3, 'div', ['id', 'second']);
{ P(4, 0, 2); }
e();
}
});
@ -466,11 +507,11 @@ describe('content projection', () => {
if (cm) {
m(0,
dP([[[['span', 'class', 'toFirst'], null]], [[['span', 'class', 'toSecond'], null]]]));
E(0, 'div', ['id', 'first']);
{ P(1, 0, 1); }
E(1, 'div', ['id', 'first']);
{ P(2, 0, 1); }
e();
E(2, 'div', ['id', 'second']);
{ P(3, 0, 2); }
E(3, 'div', ['id', 'second']);
{ P(4, 0, 2); }
e();
}
});
@ -513,11 +554,11 @@ describe('content projection', () => {
if (cm) {
m(0,
dP([[[['span', 'class', 'toFirst'], null]], [[['span', 'class', 'toSecond'], null]]]));
E(0, 'div', ['id', 'first']);
{ P(1, 0, 1); }
E(1, 'div', ['id', 'first']);
{ P(2, 0, 1); }
e();
E(2, 'div', ['id', 'second']);
{ P(3, 0, 2); }
E(3, 'div', ['id', 'second']);
{ P(4, 0, 2); }
e();
}
});
@ -559,11 +600,11 @@ describe('content projection', () => {
const Child = createComponent('child', function(ctx: any, cm: boolean) {
if (cm) {
m(0, dP([[[['span'], null]], [[['span', 'class', 'toSecond'], null]]]));
E(0, 'div', ['id', 'first']);
{ P(1, 0, 1); }
E(1, 'div', ['id', 'first']);
{ P(2, 0, 1); }
e();
E(2, 'div', ['id', 'second']);
{ P(3, 0, 2); }
E(3, 'div', ['id', 'second']);
{ P(4, 0, 2); }
e();
}
});
@ -605,11 +646,11 @@ describe('content projection', () => {
const Child = createComponent('child', function(ctx: any, cm: boolean) {
if (cm) {
m(0, dP([[[['span', 'class', 'toFirst'], null]]]));
E(0, 'div', ['id', 'first']);
{ P(1, 0, 1); }
E(1, 'div', ['id', 'first']);
{ P(2, 0, 1); }
e();
E(2, 'div', ['id', 'second']);
{ P(3, 0); }
E(3, 'div', ['id', 'second']);
{ P(4, 0); }
e();
}
});
@ -652,11 +693,11 @@ describe('content projection', () => {
const Child = createComponent('child', function(ctx: any, cm: boolean) {
if (cm) {
m(0, dP([[[['span', 'class', 'toSecond'], null]]]));
E(0, 'div', ['id', 'first']);
{ P(1, 0); }
E(1, 'div', ['id', 'first']);
{ P(2, 0); }
e();
E(2, 'div', ['id', 'second']);
{ P(3, 0, 1); }
E(3, 'div', ['id', 'second']);
{ P(4, 0, 1); }
e();
}
});
@ -706,10 +747,10 @@ describe('content projection', () => {
const GrandChild = createComponent('grand-child', function(ctx: any, cm: boolean) {
if (cm) {
m(0, dP([[[['span'], null]]]));
P(0, 0, 1);
E(1, 'hr');
P(1, 0, 1);
E(2, 'hr');
e();
P(2, 0, 0);
P(3, 0, 0);
}
});
@ -722,16 +763,16 @@ describe('content projection', () => {
const Child = createComponent('child', function(ctx: any, cm: boolean) {
if (cm) {
m(0, dP());
E(0, GrandChild.ngComponentDef);
E(1, GrandChild.ngComponentDef);
{
D(0, GrandChild.ngComponentDef.n(), GrandChild.ngComponentDef);
P(1, 0);
E(2, 'span');
{ T(3, 'in child template'); }
P(2, 0);
E(3, 'span');
{ T(4, 'in child template'); }
e();
}
e();
GrandChild.ngComponentDef.r(0, 0);
GrandChild.ngComponentDef.r(0, 1);
}
});
@ -772,8 +813,8 @@ describe('content projection', () => {
const Child = createComponent('child', function(ctx: any, cm: boolean) {
if (cm) {
m(0, dP([[[['div'], null]]]));
E(0, 'span');
{ P(1, 0, 1); }
E(1, 'span');
{ P(2, 0, 1); }
e();
}
});

View File

@ -10,7 +10,7 @@ import {ElementRef, TemplateRef, ViewContainerRef} from '@angular/core';
import {bloomFindPossibleInjector} from '../../src/render3/di';
import {C, D, E, PublicFeature, T, V, b, b2, c, defineDirective, e, inject, injectElementRef, injectTemplateRef, injectViewContainerRef, rC, rc, t, v} from '../../src/render3/index';
import {bloomAdd, createNode, createViewState, enterView, getOrCreateNodeInjector, leaveView} from '../../src/render3/instructions';
import {bloomAdd, createLNode, createViewState, enterView, getOrCreateNodeInjector, leaveView} from '../../src/render3/instructions';
import {LNodeFlags, LNodeInjector} from '../../src/render3/interfaces';
import {renderToHtml} from './render_util';
@ -321,7 +321,7 @@ describe('di', () => {
const contentView = createViewState(-1, null !);
const oldView = enterView(contentView, null !);
try {
const parent = createNode(0, LNodeFlags.Element, null, null);
const parent = createLNode(0, LNodeFlags.Element, null, null);
// Simulate the situation where the previous parent is not initialized.
// This happens on first bootstrap because we don't init existing values

View File

@ -589,4 +589,54 @@ describe('iv integration test', () => {
});
});
describe('template data', () => {
it('should re-use template data and node data', () => {
/**
* % if (condition) {
* <div></div>
* % }
*/
function Template(ctx: any, cm: boolean) {
if (cm) {
C(0);
c();
}
rC(0);
{
if (ctx.condition) {
if (V(0)) {
E(0, 'div');
{}
e();
}
v();
}
}
rc();
}
expect((Template as any).ngStaticData).toBeUndefined();
renderToHtml(Template, {condition: true});
const oldTemplateData = (Template as any).ngStaticData;
const oldContainerData = (oldTemplateData as any)[0];
const oldElementData = oldContainerData.containerStatic[0][0];
expect(oldContainerData).not.toBeNull();
expect(oldElementData).not.toBeNull();
renderToHtml(Template, {condition: false});
renderToHtml(Template, {condition: true});
const newTemplateData = (Template as any).ngStaticData;
const newContainerData = (oldTemplateData as any)[0];
const newElementData = oldContainerData.containerStatic[0][0];
expect(newTemplateData === oldTemplateData).toBe(true);
expect(newContainerData === oldContainerData).toBe(true);
expect(newElementData === oldElementData).toBe(true);
});
});
});

View File

@ -6,11 +6,18 @@
* found in the LICENSE file at https://angular.io/license
*/
import {CSSSelector, CSSSelectorWithNegations, NodeBindings, SimpleCSSSelector} from '../../src/render3/interfaces';
import {CSSSelector, CSSSelectorWithNegations, LNodeStatic, SimpleCSSSelector} from '../../src/render3/interfaces';
import {isNodeMatchingSelector, isNodeMatchingSelectorWithNegations, isNodeMatchingSimpleSelector} from '../../src/render3/node_selector_matcher';
function testLStaticData(tagName: string, attrs: string[] | null): NodeBindings {
return {tagName, attrs, initialInputs: undefined, inputs: undefined, outputs: undefined};
function testLStaticData(tagName: string, attrs: string[] | null): LNodeStatic {
return {
tagName,
attrs,
initialInputs: undefined,
inputs: undefined,
outputs: undefined,
containerStatic: null
};
}
describe('css selector matching', () => {

View File

@ -28,6 +28,17 @@ describe('outputs', () => {
});
}
let otherDir: OtherDir;
class OtherDir {
changeStream = new EventEmitter();
static ngDirectiveDef = defineDirective({
type: OtherDir,
factory: () => otherDir = new OtherDir,
outputs: {changeStream: 'change'}
});
}
it('should call component output function when event is emitted', () => {
/** <button-toggle (change)="onChange()"></button-toggle> */
@ -328,15 +339,6 @@ describe('outputs', () => {
});
it('should work with two outputs of the same name', () => {
let otherDir: OtherDir;
class OtherDir {
change = new EventEmitter();
static ngDirectiveDef = defineDirective(
{type: OtherDir, factory: () => otherDir = new OtherDir, outputs: {change: 'change'}});
}
/** <button-toggle (change)="onChange()" otherDir></button-toggle> */
function Template(ctx: any, cm: boolean) {
if (cm) {
@ -357,7 +359,7 @@ describe('outputs', () => {
buttonToggle !.change.next();
expect(counter).toEqual(1);
otherDir !.change.next();
otherDir !.changeStream.next();
expect(counter).toEqual(2);
});
@ -397,4 +399,67 @@ describe('outputs', () => {
expect(counter).toEqual(1);
});
it('should work with outputs at same index in if block', () => {
/**
* <button (click)="onClick()">Click me</button> // outputs: null
* % if (condition) {
* <button-toggle (change)="onChange()"></button-toggle> // outputs: {change: [0, 'change']}
* % } else {
* <div otherDir (change)="onChange()"></div> // outputs: {change: [0,
* 'changeStream']}
* % }
*/
function Template(ctx: any, cm: boolean) {
if (cm) {
E(0, 'button');
{
L('click', ctx.onClick.bind(ctx));
T(1, 'Click me');
}
e();
C(2);
c();
}
rC(2);
{
if (ctx.condition) {
if (V(0)) {
E(0, ButtonToggle.ngComponentDef);
{
D(0, ButtonToggle.ngComponentDef.n(), ButtonToggle.ngComponentDef);
L('change', ctx.onChange.bind(ctx));
}
e();
}
v();
} else {
if (V(1)) {
E(0, 'div');
{
D(0, OtherDir.ngDirectiveDef.n(), OtherDir.ngDirectiveDef);
L('change', ctx.onChange.bind(ctx));
}
e();
}
v();
}
}
rc();
}
let counter = 0;
const ctx = {condition: true, onChange: () => counter++, onClick: () => {}};
renderToHtml(Template, ctx);
buttonToggle !.change.next();
expect(counter).toEqual(1);
ctx.condition = false;
renderToHtml(Template, ctx);
expect(counter).toEqual(1);
otherDir !.changeStream.next();
expect(counter).toEqual(2);
});
});

View File

@ -239,6 +239,73 @@ describe('elementProperty', () => {
renderToHtml(Template, ctx);
expect(otherDir !.id).toEqual(2);
});
it('should support unrelated element properties at same index in if-else block', () => {
let idDir: IdDir;
class IdDir {
idNumber: number;
static ngDirectiveDef = defineDirective(
{type: IdDir, factory: () => idDir = new IdDir(), inputs: {idNumber: 'id'}});
}
/**
* <button idDir [id]="id1">Click me</button> // inputs: {'id': [0, 'idNumber']}
* % if (condition) {
* <button [id]="id2">Click me too</button> // inputs: null
* % } else {
* <button otherDir [id]="id3">Click me too</button> // inputs: {'id': [0, 'id']}
* % }
*/
function Template(ctx: any, cm: boolean) {
if (cm) {
E(0, 'button');
{
D(0, IdDir.ngDirectiveDef.n(), IdDir.ngDirectiveDef);
T(1, 'Click me');
}
e();
C(2);
c();
}
p(0, 'id', b(ctx.id1));
rC(2);
{
if (ctx.condition) {
if (V(0)) {
E(0, 'button');
{ T(1, 'Click me too'); }
e();
}
p(0, 'id', b(ctx.id2));
v();
} else {
if (V(1)) {
E(0, 'button');
{
D(0, OtherDir.ngDirectiveDef.n(), OtherDir.ngDirectiveDef);
T(1, 'Click me too');
}
e();
}
p(0, 'id', b(ctx.id3));
v();
}
}
rc();
}
expect(renderToHtml(Template, {condition: true, id1: 'one', id2: 'two', id3: 'three'}))
.toEqual(`<button>Click me</button><button id="two">Click me too</button>`);
expect(idDir !.idNumber).toEqual('one');
expect(renderToHtml(Template, {condition: false, id1: 'four', id2: 'two', id3: 'three'}))
.toEqual(`<button>Click me</button><button>Click me too</button>`);
expect(idDir !.idNumber).toEqual('four');
expect(otherDir !.id).toEqual('three');
});
});
describe('attributes and input properties', () => {
@ -382,6 +449,58 @@ describe('elementProperty', () => {
expect(dirB !.roleB).toEqual('listbox');
});
it('should support attributes at same index inside an if-else block', () => {
/**
* <div role="listbox" myDir></div> // initialInputs: [['role', 'listbox']]
*
* % if (condition) {
* <div role="button" myDirB></div> // initialInputs: [['role', 'button']]
* % } else {
* <div role="menu"></div> // initialInputs: [null]
* % }
*/
function Template(ctx: any, cm: boolean) {
if (cm) {
E(0, 'div', ['role', 'listbox']);
{ D(0, MyDir.ngDirectiveDef.n(), MyDir.ngDirectiveDef); }
e();
C(1);
c();
}
rC(1);
{
if (ctx.condition) {
if (V(0)) {
E(0, 'div', ['role', 'button']);
{ D(0, MyDirB.ngDirectiveDef.n(), MyDirB.ngDirectiveDef); }
e();
}
v();
} else {
if (V(1)) {
E(0, 'div', ['role', 'menu']);
{}
e();
}
v();
}
}
rc();
}
expect(renderToHtml(Template, {
condition: true
})).toEqual(`<div role="listbox"></div><div role="button"></div>`);
expect(myDir !.role).toEqual('listbox');
expect(dirB !.roleB).toEqual('button');
expect((dirB !as any).role).toBeUndefined();
expect(renderToHtml(Template, {
condition: false
})).toEqual(`<div role="listbox"></div><div role="menu"></div>`);
expect(myDir !.role).toEqual('listbox');
});
it('should process attributes properly inside a for loop', () => {
class Comp {

View File

@ -31,10 +31,10 @@ describe('query', () => {
if (cm) {
m(0, Q(Child, false));
m(1, Q(Child, true));
E(0, Child.ngComponentDef);
E(2, Child.ngComponentDef);
{
child1 = D(0, Child.ngComponentDef.n(), Child.ngComponentDef);
E(1, Child.ngComponentDef);
E(3, Child.ngComponentDef);
{ child2 = D(1, Child.ngComponentDef.n(), Child.ngComponentDef); }
e();
}

View File

@ -7,7 +7,7 @@
*/
import {ComponentTemplate, ComponentType, PublicFeature, defineComponent, renderComponent as _renderComponent} from '../../src/render3/index';
import {NG_HOST_SYMBOL, createNode, createViewState, renderTemplate} from '../../src/render3/instructions';
import {NG_HOST_SYMBOL, createLNode, createViewState, renderTemplate} from '../../src/render3/instructions';
import {LElement, LNodeFlags} from '../../src/render3/interfaces';
import {RElement, RText, Renderer3} from '../../src/render3/renderer';
import {getRenderer2} from './imported_renderer2';
@ -37,7 +37,7 @@ export function resetDOM() {
requestAnimationFrame.queue = [];
containerEl = document.createElement('div');
containerEl.setAttribute('host', '');
host = createNode(null, LNodeFlags.Element, containerEl, createViewState(-1, activeRenderer));
host = createLNode(null, LNodeFlags.Element, containerEl, createViewState(-1, activeRenderer));
// TODO: assert that the global state is clean (e.g. ngData, previousOrParentNode, etc)
}