parent
0adb97bffb
commit
65417374f1
|
@ -1,52 +0,0 @@
|
||||||
/**
|
|
||||||
* @license
|
|
||||||
* Copyright Google Inc. All Rights Reserved.
|
|
||||||
*
|
|
||||||
* Use of this source code is governed by an MIT-style license that can be
|
|
||||||
* found in the LICENSE file at https://angular.io/license
|
|
||||||
*/
|
|
||||||
|
|
||||||
import {NodeData, NodeDef, NodeFlags, NodeType, ViewData, ViewDefinition} from './types';
|
|
||||||
|
|
||||||
export function anchorDef(
|
|
||||||
flags: NodeFlags, childCount: number, template?: ViewDefinition): NodeDef {
|
|
||||||
return {
|
|
||||||
type: NodeType.Anchor,
|
|
||||||
// will bet set by the view definition
|
|
||||||
index: undefined,
|
|
||||||
reverseChildIndex: undefined,
|
|
||||||
parent: undefined,
|
|
||||||
childFlags: undefined,
|
|
||||||
bindingIndex: undefined,
|
|
||||||
disposableIndex: undefined,
|
|
||||||
providerIndices: undefined,
|
|
||||||
// regular values
|
|
||||||
flags,
|
|
||||||
childCount,
|
|
||||||
bindings: [],
|
|
||||||
disposableCount: 0,
|
|
||||||
element: undefined,
|
|
||||||
provider: undefined,
|
|
||||||
text: undefined,
|
|
||||||
component: undefined, template
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
export function createAnchor(view: ViewData, renderHost: any, def: NodeDef): NodeData {
|
|
||||||
const parentNode = def.parent != null ? view.nodes[def.parent].renderNode : renderHost;
|
|
||||||
let renderNode: any;
|
|
||||||
if (view.renderer) {
|
|
||||||
renderNode = view.renderer.createTemplateAnchor(parentNode);
|
|
||||||
} else {
|
|
||||||
renderNode = document.createComment('');
|
|
||||||
if (parentNode) {
|
|
||||||
parentNode.appendChild(renderNode);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return {
|
|
||||||
renderNode,
|
|
||||||
provider: undefined,
|
|
||||||
embeddedViews: (def.flags & NodeFlags.HasEmbeddedViews) ? [] : undefined,
|
|
||||||
componentView: undefined
|
|
||||||
};
|
|
||||||
}
|
|
|
@ -8,9 +8,33 @@
|
||||||
|
|
||||||
import {SecurityContext} from '../security';
|
import {SecurityContext} from '../security';
|
||||||
|
|
||||||
import {BindingDef, BindingType, DisposableFn, ElementOutputDef, NodeData, NodeDef, NodeFlags, NodeType, ViewData, ViewFlags} from './types';
|
import {BindingDef, BindingType, DisposableFn, ElementOutputDef, NodeData, NodeDef, NodeFlags, NodeType, ViewData, ViewDefinition, ViewFlags} from './types';
|
||||||
import {checkAndUpdateBinding, setBindingDebugInfo} from './util';
|
import {checkAndUpdateBinding, setBindingDebugInfo} from './util';
|
||||||
|
|
||||||
|
export function anchorDef(
|
||||||
|
flags: NodeFlags, childCount: number, template?: ViewDefinition): NodeDef {
|
||||||
|
return {
|
||||||
|
type: NodeType.Element,
|
||||||
|
// will bet set by the view definition
|
||||||
|
index: undefined,
|
||||||
|
reverseChildIndex: undefined,
|
||||||
|
parent: undefined,
|
||||||
|
childFlags: undefined,
|
||||||
|
bindingIndex: undefined,
|
||||||
|
disposableIndex: undefined,
|
||||||
|
providerIndices: undefined,
|
||||||
|
// regular values
|
||||||
|
flags,
|
||||||
|
childCount,
|
||||||
|
bindings: [],
|
||||||
|
disposableCount: 0,
|
||||||
|
element: {name: undefined, attrs: undefined, outputs: [], template},
|
||||||
|
provider: undefined,
|
||||||
|
text: undefined,
|
||||||
|
pureExpression: undefined
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
export function elementDef(
|
export function elementDef(
|
||||||
flags: NodeFlags, childCount: number, name: string, fixedAttrs: {[name: string]: string} = {},
|
flags: NodeFlags, childCount: number, name: string, fixedAttrs: {[name: string]: string} = {},
|
||||||
bindings?:
|
bindings?:
|
||||||
|
@ -65,11 +89,10 @@ export function elementDef(
|
||||||
childCount,
|
childCount,
|
||||||
bindings: bindingDefs,
|
bindings: bindingDefs,
|
||||||
disposableCount: outputDefs.length,
|
disposableCount: outputDefs.length,
|
||||||
element: {name, attrs: fixedAttrs, outputs: outputDefs},
|
element: {name, attrs: fixedAttrs, outputs: outputDefs, template: undefined},
|
||||||
provider: undefined,
|
provider: undefined,
|
||||||
text: undefined,
|
text: undefined,
|
||||||
component: undefined,
|
pureExpression: undefined
|
||||||
template: undefined
|
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -78,9 +101,10 @@ export function createElement(view: ViewData, renderHost: any, def: NodeDef): No
|
||||||
const elDef = def.element;
|
const elDef = def.element;
|
||||||
let el: any;
|
let el: any;
|
||||||
if (view.renderer) {
|
if (view.renderer) {
|
||||||
el = view.renderer.createElement(parentNode, elDef.name);
|
el = elDef.name ? view.renderer.createElement(parentNode, elDef.name) :
|
||||||
|
view.renderer.createTemplateAnchor(parentNode);
|
||||||
} else {
|
} else {
|
||||||
el = document.createElement(elDef.name);
|
el = elDef.name ? document.createElement(elDef.name) : document.createComment('');
|
||||||
if (parentNode) {
|
if (parentNode) {
|
||||||
parentNode.appendChild(el);
|
parentNode.appendChild(el);
|
||||||
}
|
}
|
||||||
|
|
|
@ -6,9 +6,9 @@
|
||||||
* found in the LICENSE file at https://angular.io/license
|
* found in the LICENSE file at https://angular.io/license
|
||||||
*/
|
*/
|
||||||
|
|
||||||
export {anchorDef} from './anchor';
|
export {anchorDef, elementDef} from './element';
|
||||||
export {elementDef} from './element';
|
|
||||||
export {providerDef} from './provider';
|
export {providerDef} from './provider';
|
||||||
|
export {pureArrayDef, pureObjectDef, purePipeDef} from './pure_expression';
|
||||||
export {textDef} from './text';
|
export {textDef} from './text';
|
||||||
export {checkAndUpdateView, checkNoChangesView, createEmbeddedView, createRootView, destroyView, viewDef} from './view';
|
export {checkAndUpdateView, checkNoChangesView, createEmbeddedView, createRootView, destroyView, viewDef} from './view';
|
||||||
export {attachEmbeddedView, detachEmbeddedView, rootRenderNodes} from './view_attach';
|
export {attachEmbeddedView, detachEmbeddedView, rootRenderNodes} from './view_attach';
|
||||||
|
|
|
@ -75,9 +75,9 @@ export function providerDef(
|
||||||
childCount: 0, bindings,
|
childCount: 0, bindings,
|
||||||
disposableCount: outputDefs.length,
|
disposableCount: outputDefs.length,
|
||||||
element: undefined,
|
element: undefined,
|
||||||
provider: {tokenKey: tokenKey(ctor), ctor, deps: depDefs, outputs: outputDefs},
|
provider: {tokenKey: tokenKey(ctor), ctor, deps: depDefs, outputs: outputDefs, component},
|
||||||
text: undefined, component,
|
text: undefined,
|
||||||
template: undefined
|
pureExpression: undefined
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -147,8 +147,7 @@ export function checkAndUpdateProviderInline(
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export function checkAndUpdateProviderDynamic(
|
export function checkAndUpdateProviderDynamic(view: ViewData, def: NodeDef, values: any[]) {
|
||||||
view: ViewData, index: number, def: NodeDef, values: any[]) {
|
|
||||||
const provider = view.nodes[def.index].provider;
|
const provider = view.nodes[def.index].provider;
|
||||||
let changes: SimpleChanges;
|
let changes: SimpleChanges;
|
||||||
for (let i = 0; i < values.length; i++) {
|
for (let i = 0; i < values.length; i++) {
|
||||||
|
|
|
@ -0,0 +1,227 @@
|
||||||
|
/**
|
||||||
|
* @license
|
||||||
|
* Copyright Google Inc. All Rights Reserved.
|
||||||
|
*
|
||||||
|
* Use of this source code is governed by an MIT-style license that can be
|
||||||
|
* found in the LICENSE file at https://angular.io/license
|
||||||
|
*/
|
||||||
|
|
||||||
|
import {resolveDep, tokenKey} from './provider';
|
||||||
|
import {BindingDef, BindingType, DepDef, DepFlags, NodeData, NodeDef, NodeType, PureExpressionData, PureExpressionType, ViewData} from './types';
|
||||||
|
import {checkAndUpdateBinding} from './util';
|
||||||
|
|
||||||
|
export function purePipeDef(pipeToken: any, argCount: number): NodeDef {
|
||||||
|
return _pureExpressionDef(
|
||||||
|
PureExpressionType.Pipe, new Array(argCount),
|
||||||
|
{token: pipeToken, tokenKey: tokenKey(pipeToken), flags: DepFlags.None});
|
||||||
|
}
|
||||||
|
|
||||||
|
export function pureArrayDef(argCount: number): NodeDef {
|
||||||
|
return _pureExpressionDef(PureExpressionType.Array, new Array(argCount), undefined);
|
||||||
|
}
|
||||||
|
|
||||||
|
export function pureObjectDef(propertyNames: string[]): NodeDef {
|
||||||
|
return _pureExpressionDef(PureExpressionType.Object, propertyNames, undefined);
|
||||||
|
}
|
||||||
|
|
||||||
|
function _pureExpressionDef(
|
||||||
|
type: PureExpressionType, propertyNames: string[], pipeDep: DepDef): NodeDef {
|
||||||
|
const bindings: BindingDef[] = new Array(propertyNames.length);
|
||||||
|
for (let i = 0; i < propertyNames.length; i++) {
|
||||||
|
const prop = propertyNames[i];
|
||||||
|
bindings[i] = {
|
||||||
|
type: BindingType.PureExpressionProperty,
|
||||||
|
name: prop,
|
||||||
|
nonMinifiedName: prop,
|
||||||
|
securityContext: undefined,
|
||||||
|
suffix: undefined
|
||||||
|
};
|
||||||
|
}
|
||||||
|
return {
|
||||||
|
type: NodeType.PureExpression,
|
||||||
|
// will bet set by the view definition
|
||||||
|
index: undefined,
|
||||||
|
reverseChildIndex: undefined,
|
||||||
|
parent: undefined,
|
||||||
|
childFlags: undefined,
|
||||||
|
bindingIndex: undefined,
|
||||||
|
disposableIndex: undefined,
|
||||||
|
providerIndices: undefined,
|
||||||
|
// regular values
|
||||||
|
flags: 0,
|
||||||
|
childCount: 0, bindings,
|
||||||
|
disposableCount: 0,
|
||||||
|
element: undefined,
|
||||||
|
provider: undefined,
|
||||||
|
text: undefined,
|
||||||
|
pureExpression: {type, pipeDep}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
export function createPureExpression(view: ViewData, def: NodeDef): NodeData {
|
||||||
|
const pipe = def.pureExpression.pipeDep ?
|
||||||
|
resolveDep(view, def.parent, def.pureExpression.pipeDep) :
|
||||||
|
undefined;
|
||||||
|
const data: PureExpressionData = {value: undefined, pipe: pipe};
|
||||||
|
return {
|
||||||
|
renderNode: undefined,
|
||||||
|
provider: data,
|
||||||
|
embeddedViews: undefined,
|
||||||
|
componentView: undefined
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
export function checkAndUpdatePureExpressionInline(
|
||||||
|
view: ViewData, def: NodeDef, v0: any, v1: any, v2: any, v3: any, v4: any, v5: any, v6: any,
|
||||||
|
v7: any, v8: any, v9: any) {
|
||||||
|
const bindings = def.bindings;
|
||||||
|
let changed = false;
|
||||||
|
// Note: fallthrough is intended!
|
||||||
|
switch (bindings.length) {
|
||||||
|
case 10:
|
||||||
|
if (checkAndUpdateBinding(view, def, 9, v9)) changed = true;
|
||||||
|
case 9:
|
||||||
|
if (checkAndUpdateBinding(view, def, 8, v8)) changed = true;
|
||||||
|
case 8:
|
||||||
|
if (checkAndUpdateBinding(view, def, 7, v7)) changed = true;
|
||||||
|
case 7:
|
||||||
|
if (checkAndUpdateBinding(view, def, 6, v6)) changed = true;
|
||||||
|
case 6:
|
||||||
|
if (checkAndUpdateBinding(view, def, 5, v5)) changed = true;
|
||||||
|
case 5:
|
||||||
|
if (checkAndUpdateBinding(view, def, 4, v4)) changed = true;
|
||||||
|
case 4:
|
||||||
|
if (checkAndUpdateBinding(view, def, 3, v3)) changed = true;
|
||||||
|
case 3:
|
||||||
|
if (checkAndUpdateBinding(view, def, 2, v2)) changed = true;
|
||||||
|
case 2:
|
||||||
|
if (checkAndUpdateBinding(view, def, 1, v1)) changed = true;
|
||||||
|
case 1:
|
||||||
|
if (checkAndUpdateBinding(view, def, 0, v0)) changed = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (changed) {
|
||||||
|
const data: PureExpressionData = view.nodes[def.index].provider;
|
||||||
|
let value: any;
|
||||||
|
switch (def.pureExpression.type) {
|
||||||
|
case PureExpressionType.Array:
|
||||||
|
value = new Array(bindings.length);
|
||||||
|
// Note: fallthrough is intended!
|
||||||
|
switch (bindings.length) {
|
||||||
|
case 10:
|
||||||
|
value[9] = v9;
|
||||||
|
case 9:
|
||||||
|
value[8] = v8;
|
||||||
|
case 8:
|
||||||
|
value[7] = v7;
|
||||||
|
case 7:
|
||||||
|
value[6] = v6;
|
||||||
|
case 6:
|
||||||
|
value[5] = v5;
|
||||||
|
case 5:
|
||||||
|
value[4] = v4;
|
||||||
|
case 4:
|
||||||
|
value[3] = v3;
|
||||||
|
case 3:
|
||||||
|
value[2] = v2;
|
||||||
|
case 2:
|
||||||
|
value[1] = v1;
|
||||||
|
case 1:
|
||||||
|
value[0] = v0;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case PureExpressionType.Object:
|
||||||
|
value = {};
|
||||||
|
// Note: fallthrough is intended!
|
||||||
|
switch (bindings.length) {
|
||||||
|
case 10:
|
||||||
|
value[bindings[9].name] = v9;
|
||||||
|
case 9:
|
||||||
|
value[bindings[8].name] = v8;
|
||||||
|
case 8:
|
||||||
|
value[bindings[7].name] = v7;
|
||||||
|
case 7:
|
||||||
|
value[bindings[6].name] = v6;
|
||||||
|
case 6:
|
||||||
|
value[bindings[5].name] = v5;
|
||||||
|
case 5:
|
||||||
|
value[bindings[4].name] = v4;
|
||||||
|
case 4:
|
||||||
|
value[bindings[3].name] = v3;
|
||||||
|
case 3:
|
||||||
|
value[bindings[2].name] = v2;
|
||||||
|
case 2:
|
||||||
|
value[bindings[1].name] = v1;
|
||||||
|
case 1:
|
||||||
|
value[bindings[0].name] = v0;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case PureExpressionType.Pipe:
|
||||||
|
switch (bindings.length) {
|
||||||
|
case 10:
|
||||||
|
value = data.pipe.transform(v0, v1, v2, v3, v4, v5, v6, v7, v8, v9);
|
||||||
|
break;
|
||||||
|
case 9:
|
||||||
|
value = data.pipe.transform(v0, v1, v2, v3, v4, v5, v6, v7, v8);
|
||||||
|
break;
|
||||||
|
case 8:
|
||||||
|
value = data.pipe.transform(v0, v1, v2, v3, v4, v5, v6, v7);
|
||||||
|
break;
|
||||||
|
case 7:
|
||||||
|
value = data.pipe.transform(v0, v1, v2, v3, v4, v5, v6);
|
||||||
|
break;
|
||||||
|
case 6:
|
||||||
|
value = data.pipe.transform(v0, v1, v2, v3, v4, v5);
|
||||||
|
break;
|
||||||
|
case 5:
|
||||||
|
value = data.pipe.transform(v0, v1, v2, v3, v4);
|
||||||
|
break;
|
||||||
|
case 4:
|
||||||
|
value = data.pipe.transform(v0, v1, v2, v3);
|
||||||
|
break;
|
||||||
|
case 3:
|
||||||
|
value = data.pipe.transform(v0, v1, v2);
|
||||||
|
break;
|
||||||
|
case 2:
|
||||||
|
value = data.pipe.transform(v0, v1);
|
||||||
|
break;
|
||||||
|
case 1:
|
||||||
|
value = data.pipe.transform(v0);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
data.value = value;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export function checkAndUpdatePureExpressionDynamic(view: ViewData, def: NodeDef, values: any[]) {
|
||||||
|
const bindings = def.bindings;
|
||||||
|
let changed = false;
|
||||||
|
for (let i = 0; i < values.length; i++) {
|
||||||
|
// Note: We need to loop over all values, so that
|
||||||
|
// the old values are updates as well!
|
||||||
|
if (checkAndUpdateBinding(view, def, i, values[i])) {
|
||||||
|
changed = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (changed) {
|
||||||
|
const data: PureExpressionData = view.nodes[def.index].provider;
|
||||||
|
let value: any;
|
||||||
|
switch (def.pureExpression.type) {
|
||||||
|
case PureExpressionType.Array:
|
||||||
|
value = values;
|
||||||
|
break;
|
||||||
|
case PureExpressionType.Object:
|
||||||
|
value = {};
|
||||||
|
for (let i = 0; i < values.length; i++) {
|
||||||
|
value[bindings[i].name] = values[i];
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case PureExpressionType.Pipe:
|
||||||
|
value = data.pipe.transform(values[0], ...values.slice(1));
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
data.value = value;
|
||||||
|
}
|
||||||
|
}
|
|
@ -39,8 +39,7 @@ export function textDef(constants: string[]): NodeDef {
|
||||||
element: undefined,
|
element: undefined,
|
||||||
provider: undefined,
|
provider: undefined,
|
||||||
text: {prefix: constants[0]},
|
text: {prefix: constants[0]},
|
||||||
component: undefined,
|
pureExpression: undefined
|
||||||
template: undefined
|
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -124,9 +123,13 @@ export function checkAndUpdateTextInline(
|
||||||
|
|
||||||
export function checkAndUpdateTextDynamic(view: ViewData, def: NodeDef, values: any[]) {
|
export function checkAndUpdateTextDynamic(view: ViewData, def: NodeDef, values: any[]) {
|
||||||
const bindings = def.bindings;
|
const bindings = def.bindings;
|
||||||
let changed = view.firstChange;
|
let changed = false;
|
||||||
for (let i = 0; i < values.length && !changed; i++) {
|
for (let i = 0; i < values.length; i++) {
|
||||||
changed = changed || checkAndUpdateBinding(view, def, i, values[i]);
|
// Note: We need to loop over all values, so that
|
||||||
|
// the old values are updates as well!
|
||||||
|
if (checkAndUpdateBinding(view, def, i, values[i])) {
|
||||||
|
changed = true;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
if (changed) {
|
if (changed) {
|
||||||
let value = '';
|
let value = '';
|
||||||
|
|
|
@ -6,6 +6,7 @@
|
||||||
* found in the LICENSE file at https://angular.io/license
|
* found in the LICENSE file at https://angular.io/license
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
import {PipeTransform} from '../change_detection/change_detection';
|
||||||
import {TemplateRef} from '../linker/template_ref';
|
import {TemplateRef} from '../linker/template_ref';
|
||||||
import {ViewContainerRef} from '../linker/view_container_ref';
|
import {ViewContainerRef} from '../linker/view_container_ref';
|
||||||
import {RenderComponentType, Renderer, RootRenderer} from '../render/api';
|
import {RenderComponentType, Renderer, RootRenderer} from '../render/api';
|
||||||
|
@ -42,8 +43,8 @@ export type ViewUpdateFn = (updater: NodeUpdater, view: ViewData) => void;
|
||||||
export interface NodeUpdater {
|
export interface NodeUpdater {
|
||||||
checkInline(
|
checkInline(
|
||||||
view: ViewData, nodeIndex: number, v0?: any, v1?: any, v2?: any, v3?: any, v4?: any, v5?: any,
|
view: ViewData, nodeIndex: number, v0?: any, v1?: any, v2?: any, v3?: any, v4?: any, v5?: any,
|
||||||
v6?: any, v7?: any, v8?: any, v9?: any): void;
|
v6?: any, v7?: any, v8?: any, v9?: any): any;
|
||||||
checkDynamic(view: ViewData, nodeIndex: number, values: any[]): void;
|
checkDynamic(view: ViewData, nodeIndex: number, values: any[]): any;
|
||||||
}
|
}
|
||||||
|
|
||||||
export type ViewHandleEventFn =
|
export type ViewHandleEventFn =
|
||||||
|
@ -68,24 +69,22 @@ export interface NodeDef {
|
||||||
childCount: number;
|
childCount: number;
|
||||||
/** aggregated NodeFlags for all children **/
|
/** aggregated NodeFlags for all children **/
|
||||||
childFlags: NodeFlags;
|
childFlags: NodeFlags;
|
||||||
|
providerIndices: {[tokenKey: string]: number};
|
||||||
bindingIndex: number;
|
bindingIndex: number;
|
||||||
bindings: BindingDef[];
|
bindings: BindingDef[];
|
||||||
disposableIndex: number;
|
disposableIndex: number;
|
||||||
disposableCount: number;
|
disposableCount: number;
|
||||||
element: ElementDef;
|
element: ElementDef;
|
||||||
providerIndices: {[tokenKey: string]: number};
|
|
||||||
provider: ProviderDef;
|
provider: ProviderDef;
|
||||||
text: TextDef;
|
text: TextDef;
|
||||||
// closure to allow recursive components
|
pureExpression: PureExpressionDef;
|
||||||
component: () => ViewDefinition;
|
|
||||||
template: ViewDefinition;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export enum NodeType {
|
export enum NodeType {
|
||||||
Element,
|
Element,
|
||||||
Text,
|
Text,
|
||||||
Anchor,
|
Provider,
|
||||||
Provider
|
PureExpression
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -109,6 +108,7 @@ export interface ElementDef {
|
||||||
name: string;
|
name: string;
|
||||||
attrs: {[name: string]: string};
|
attrs: {[name: string]: string};
|
||||||
outputs: ElementOutputDef[];
|
outputs: ElementOutputDef[];
|
||||||
|
template: ViewDefinition;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface ElementOutputDef {
|
export interface ElementOutputDef {
|
||||||
|
@ -140,17 +140,31 @@ export interface ProviderDef {
|
||||||
ctor: any;
|
ctor: any;
|
||||||
deps: DepDef[];
|
deps: DepDef[];
|
||||||
outputs: ProviderOutputDef[];
|
outputs: ProviderOutputDef[];
|
||||||
|
// closure to allow recursive components
|
||||||
|
component: () => ViewDefinition;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface TextDef { prefix: string; }
|
export interface TextDef { prefix: string; }
|
||||||
|
|
||||||
|
export interface PureExpressionDef {
|
||||||
|
type: PureExpressionType;
|
||||||
|
pipeDep: DepDef;
|
||||||
|
}
|
||||||
|
|
||||||
|
export enum PureExpressionType {
|
||||||
|
Array,
|
||||||
|
Object,
|
||||||
|
Pipe
|
||||||
|
}
|
||||||
|
|
||||||
export enum BindingType {
|
export enum BindingType {
|
||||||
ElementAttribute,
|
ElementAttribute,
|
||||||
ElementClass,
|
ElementClass,
|
||||||
ElementStyle,
|
ElementStyle,
|
||||||
ElementProperty,
|
ElementProperty,
|
||||||
ProviderProperty,
|
ProviderProperty,
|
||||||
Interpolation
|
Interpolation,
|
||||||
|
PureExpressionProperty
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface BindingDef {
|
export interface BindingDef {
|
||||||
|
@ -193,11 +207,16 @@ export type DisposableFn = () => void;
|
||||||
*/
|
*/
|
||||||
export interface NodeData {
|
export interface NodeData {
|
||||||
renderNode: any;
|
renderNode: any;
|
||||||
provider: any;
|
provider: PureExpressionData|any;
|
||||||
componentView: ViewData;
|
componentView: ViewData;
|
||||||
embeddedViews: ViewData[];
|
embeddedViews: ViewData[];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export interface PureExpressionData {
|
||||||
|
value: any;
|
||||||
|
pipe: PipeTransform;
|
||||||
|
}
|
||||||
|
|
||||||
export interface Services {
|
export interface Services {
|
||||||
renderComponent(rcp: RenderComponentType): Renderer;
|
renderComponent(rcp: RenderComponentType): Renderer;
|
||||||
sanitize(context: SecurityContext, value: string): string;
|
sanitize(context: SecurityContext, value: string): string;
|
||||||
|
|
|
@ -9,11 +9,11 @@
|
||||||
import {ExpressionChangedAfterItHasBeenCheckedError} from '../linker/errors';
|
import {ExpressionChangedAfterItHasBeenCheckedError} from '../linker/errors';
|
||||||
import {RenderComponentType, Renderer} from '../render/api';
|
import {RenderComponentType, Renderer} from '../render/api';
|
||||||
|
|
||||||
import {createAnchor} from './anchor';
|
|
||||||
import {checkAndUpdateElementDynamic, checkAndUpdateElementInline, createElement} from './element';
|
import {checkAndUpdateElementDynamic, checkAndUpdateElementInline, createElement} from './element';
|
||||||
import {callLifecycleHooksChildrenFirst, checkAndUpdateProviderDynamic, checkAndUpdateProviderInline, createProvider} from './provider';
|
import {callLifecycleHooksChildrenFirst, checkAndUpdateProviderDynamic, checkAndUpdateProviderInline, createProvider} from './provider';
|
||||||
|
import {checkAndUpdatePureExpressionDynamic, checkAndUpdatePureExpressionInline, createPureExpression} from './pure_expression';
|
||||||
import {checkAndUpdateTextDynamic, checkAndUpdateTextInline, createText} from './text';
|
import {checkAndUpdateTextDynamic, checkAndUpdateTextInline, createText} from './text';
|
||||||
import {ElementDef, NodeData, NodeDef, NodeFlags, NodeType, NodeUpdater, ProviderDef, Services, TextDef, ViewData, ViewDefinition, ViewFlags, ViewHandleEventFn, ViewUpdateFn} from './types';
|
import {ElementDef, NodeData, NodeDef, NodeFlags, NodeType, NodeUpdater, ProviderDef, PureExpressionData, Services, TextDef, ViewData, ViewDefinition, ViewFlags, ViewHandleEventFn, ViewUpdateFn} from './types';
|
||||||
import {checkBindingNoChanges} from './util';
|
import {checkBindingNoChanges} from './util';
|
||||||
|
|
||||||
const NOOP = (): any => undefined;
|
const NOOP = (): any => undefined;
|
||||||
|
@ -123,16 +123,17 @@ function calculateReverseChildIndex(
|
||||||
}
|
}
|
||||||
|
|
||||||
function validateNode(parent: NodeDef, node: NodeDef) {
|
function validateNode(parent: NodeDef, node: NodeDef) {
|
||||||
if (node.template) {
|
const template = node.element && node.element.template;
|
||||||
if (node.template.lastRootNode != null &&
|
if (template) {
|
||||||
node.template.nodes[node.template.lastRootNode].flags & NodeFlags.HasEmbeddedViews) {
|
if (template.lastRootNode != null &&
|
||||||
|
template.nodes[template.lastRootNode].flags & NodeFlags.HasEmbeddedViews) {
|
||||||
throw new Error(
|
throw new Error(
|
||||||
`Illegal State: Last root node of a template can't have embedded views, at index ${node.index}!`);
|
`Illegal State: Last root node of a template can't have embedded views, at index ${node.index}!`);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (node.provider) {
|
if (node.provider) {
|
||||||
const parentType = parent ? parent.type : null;
|
const parentType = parent ? parent.type : null;
|
||||||
if (parentType !== NodeType.Element && parentType !== NodeType.Anchor) {
|
if (parentType !== NodeType.Element) {
|
||||||
throw new Error(
|
throw new Error(
|
||||||
`Illegal State: Provider nodes need to be children of elements or anchors, at index ${node.index}!`);
|
`Illegal State: Provider nodes need to be children of elements or anchors, at index ${node.index}!`);
|
||||||
}
|
}
|
||||||
|
@ -175,7 +176,7 @@ function cloneAndModifyNode(nodeDef: NodeDef, values: {
|
||||||
export function createEmbeddedView(parent: ViewData, anchorDef: NodeDef, context?: any): ViewData {
|
export function createEmbeddedView(parent: ViewData, anchorDef: NodeDef, context?: any): ViewData {
|
||||||
// embedded views are seen as siblings to the anchor, so we need
|
// embedded views are seen as siblings to the anchor, so we need
|
||||||
// to get the parent of the anchor and use it as parentIndex.
|
// to get the parent of the anchor and use it as parentIndex.
|
||||||
const view = createView(parent.services, parent, anchorDef.parent, anchorDef.template);
|
const view = createView(parent.services, parent, anchorDef.parent, anchorDef.element.template);
|
||||||
initView(view, null, parent.component, context);
|
initView(view, null, parent.component, context);
|
||||||
return view;
|
return view;
|
||||||
}
|
}
|
||||||
|
@ -223,16 +224,16 @@ function initView(view: ViewData, renderHost: any, component: any, context: any)
|
||||||
case NodeType.Text:
|
case NodeType.Text:
|
||||||
nodeData = createText(view, renderHost, nodeDef);
|
nodeData = createText(view, renderHost, nodeDef);
|
||||||
break;
|
break;
|
||||||
case NodeType.Anchor:
|
|
||||||
nodeData = createAnchor(view, renderHost, nodeDef);
|
|
||||||
break;
|
|
||||||
case NodeType.Provider:
|
case NodeType.Provider:
|
||||||
let componentView: ViewData;
|
let componentView: ViewData;
|
||||||
if (nodeDef.component) {
|
if (nodeDef.provider.component) {
|
||||||
componentView = createView(view.services, view, i, nodeDef.component());
|
componentView = createView(view.services, view, i, nodeDef.provider.component());
|
||||||
}
|
}
|
||||||
nodeData = createProvider(view, nodeDef, componentView);
|
nodeData = createProvider(view, nodeDef, componentView);
|
||||||
break;
|
break;
|
||||||
|
case NodeType.PureExpression:
|
||||||
|
nodeData = createPureExpression(view, nodeDef);
|
||||||
|
break;
|
||||||
}
|
}
|
||||||
nodes[i] = nodeData;
|
nodes[i] = nodeData;
|
||||||
}
|
}
|
||||||
|
@ -272,12 +273,22 @@ const CheckNoChanges: NodeUpdater = {
|
||||||
case 1:
|
case 1:
|
||||||
checkBindingNoChanges(view, nodeDef, 0, v0);
|
checkBindingNoChanges(view, nodeDef, 0, v0);
|
||||||
}
|
}
|
||||||
|
if (nodeDef.type === NodeType.PureExpression) {
|
||||||
|
const data: PureExpressionData = view.nodes[index].provider;
|
||||||
|
return data.value;
|
||||||
|
}
|
||||||
|
return undefined;
|
||||||
},
|
},
|
||||||
checkDynamic: (view: ViewData, index: number, values: any[]): void => {
|
checkDynamic: (view: ViewData, index: number, values: any[]): void => {
|
||||||
const oldValues = view.oldValues;
|
const nodeDef = view.def.nodes[index];
|
||||||
for (let i = 0; i < values.length; i++) {
|
for (let i = 0; i < values.length; i++) {
|
||||||
checkBindingNoChanges(view, view.def.nodes[index], i, values[i]);
|
checkBindingNoChanges(view, nodeDef, i, values[i]);
|
||||||
}
|
}
|
||||||
|
if (nodeDef.type === NodeType.PureExpression) {
|
||||||
|
const data: PureExpressionData = view.nodes[index].provider;
|
||||||
|
return data.value;
|
||||||
|
}
|
||||||
|
return undefined;
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -301,13 +312,17 @@ const CheckAndUpdate: NodeUpdater = {
|
||||||
switch (nodeDef.type) {
|
switch (nodeDef.type) {
|
||||||
case NodeType.Element:
|
case NodeType.Element:
|
||||||
checkAndUpdateElementInline(view, nodeDef, v0, v1, v2, v3, v4, v5, v6, v7, v8, v9);
|
checkAndUpdateElementInline(view, nodeDef, v0, v1, v2, v3, v4, v5, v6, v7, v8, v9);
|
||||||
break;
|
return undefined;
|
||||||
case NodeType.Text:
|
case NodeType.Text:
|
||||||
checkAndUpdateTextInline(view, nodeDef, v0, v1, v2, v3, v4, v5, v6, v7, v8, v9);
|
checkAndUpdateTextInline(view, nodeDef, v0, v1, v2, v3, v4, v5, v6, v7, v8, v9);
|
||||||
break;
|
return undefined;
|
||||||
case NodeType.Provider:
|
case NodeType.Provider:
|
||||||
checkAndUpdateProviderInline(view, nodeDef, v0, v1, v2, v3, v4, v5, v6, v7, v8, v9);
|
checkAndUpdateProviderInline(view, nodeDef, v0, v1, v2, v3, v4, v5, v6, v7, v8, v9);
|
||||||
break;
|
return undefined;
|
||||||
|
case NodeType.PureExpression:
|
||||||
|
checkAndUpdatePureExpressionInline(view, nodeDef, v0, v1, v2, v3, v4, v5, v6, v7, v8, v9);
|
||||||
|
const data: PureExpressionData = view.nodes[index].provider;
|
||||||
|
return data.value;
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
checkDynamic: (view: ViewData, index: number, values: any[]): void => {
|
checkDynamic: (view: ViewData, index: number, values: any[]): void => {
|
||||||
|
@ -315,13 +330,17 @@ const CheckAndUpdate: NodeUpdater = {
|
||||||
switch (nodeDef.type) {
|
switch (nodeDef.type) {
|
||||||
case NodeType.Element:
|
case NodeType.Element:
|
||||||
checkAndUpdateElementDynamic(view, nodeDef, values);
|
checkAndUpdateElementDynamic(view, nodeDef, values);
|
||||||
break;
|
return undefined;
|
||||||
case NodeType.Text:
|
case NodeType.Text:
|
||||||
checkAndUpdateTextDynamic(view, nodeDef, values);
|
checkAndUpdateTextDynamic(view, nodeDef, values);
|
||||||
break;
|
return undefined;
|
||||||
case NodeType.Provider:
|
case NodeType.Provider:
|
||||||
checkAndUpdateProviderDynamic(view, index, nodeDef, values);
|
checkAndUpdateProviderDynamic(view, nodeDef, values);
|
||||||
break;
|
return undefined;
|
||||||
|
case NodeType.PureExpression:
|
||||||
|
checkAndUpdatePureExpressionDynamic(view, nodeDef, values);
|
||||||
|
const data: PureExpressionData = view.nodes[index].provider;
|
||||||
|
return data.value;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
|
@ -7,6 +7,7 @@
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import {RootRenderer} from '@angular/core';
|
import {RootRenderer} from '@angular/core';
|
||||||
|
import {NodeUpdater, ViewData} from '@angular/core/src/view/index';
|
||||||
import {TestBed} from '@angular/core/testing';
|
import {TestBed} from '@angular/core/testing';
|
||||||
import {getDOM} from '@angular/platform-browser/src/dom/dom_adapter';
|
import {getDOM} from '@angular/platform-browser/src/dom/dom_adapter';
|
||||||
|
|
||||||
|
@ -34,3 +35,21 @@ export function setupAndCheckRenderer(config: {directDom: boolean}) {
|
||||||
afterEach(() => { expect(rootRenderer.renderComponent).toHaveBeenCalled(); });
|
afterEach(() => { expect(rootRenderer.renderComponent).toHaveBeenCalled(); });
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export enum InlineDynamic {
|
||||||
|
Inline,
|
||||||
|
Dynamic
|
||||||
|
}
|
||||||
|
|
||||||
|
export const INLINE_DYNAMIC_VALUES = [InlineDynamic.Inline, InlineDynamic.Dynamic];
|
||||||
|
|
||||||
|
export function callUpdater(
|
||||||
|
updater: NodeUpdater, inlineDynamic: InlineDynamic, view: ViewData, nodeIndex: number,
|
||||||
|
values: any[]): any {
|
||||||
|
switch (inlineDynamic) {
|
||||||
|
case InlineDynamic.Inline:
|
||||||
|
return (<any>updater.checkInline)(view, nodeIndex, ...values);
|
||||||
|
case InlineDynamic.Dynamic:
|
||||||
|
return updater.checkDynamic(view, nodeIndex, values);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
@ -0,0 +1,149 @@
|
||||||
|
/**
|
||||||
|
* @license
|
||||||
|
* Copyright Google Inc. All Rights Reserved.
|
||||||
|
*
|
||||||
|
* Use of this source code is governed by an MIT-style license that can be
|
||||||
|
* found in the LICENSE file at https://angular.io/license
|
||||||
|
*/
|
||||||
|
|
||||||
|
import {PipeTransform, RenderComponentType, RootRenderer, Sanitizer, SecurityContext, ViewEncapsulation} from '@angular/core';
|
||||||
|
import {DefaultServices, NodeDef, NodeFlags, NodeUpdater, Services, ViewData, ViewDefinition, ViewFlags, ViewHandleEventFn, ViewUpdateFn, anchorDef, checkAndUpdateView, checkNoChangesView, createRootView, elementDef, providerDef, pureArrayDef, pureObjectDef, purePipeDef, rootRenderNodes, textDef, viewDef} from '@angular/core/src/view/index';
|
||||||
|
import {inject} from '@angular/core/testing';
|
||||||
|
|
||||||
|
import {INLINE_DYNAMIC_VALUES, InlineDynamic, callUpdater} from './helper';
|
||||||
|
|
||||||
|
export function main() {
|
||||||
|
describe(`View Pure Expressions`, () => {
|
||||||
|
let services: Services;
|
||||||
|
let renderComponentType: RenderComponentType;
|
||||||
|
|
||||||
|
beforeEach(
|
||||||
|
inject([RootRenderer, Sanitizer], (rootRenderer: RootRenderer, sanitizer: Sanitizer) => {
|
||||||
|
services = new DefaultServices(rootRenderer, sanitizer);
|
||||||
|
renderComponentType =
|
||||||
|
new RenderComponentType('1', 'someUrl', 0, ViewEncapsulation.None, [], {});
|
||||||
|
}));
|
||||||
|
|
||||||
|
function compViewDef(
|
||||||
|
nodes: NodeDef[], update?: ViewUpdateFn, handleEvent?: ViewHandleEventFn): ViewDefinition {
|
||||||
|
return viewDef(ViewFlags.None, nodes, update, handleEvent, renderComponentType);
|
||||||
|
}
|
||||||
|
|
||||||
|
function createAndGetRootNodes(viewDef: ViewDefinition): {rootNodes: any[], view: ViewData} {
|
||||||
|
const view = createRootView(services, viewDef);
|
||||||
|
const rootNodes = rootRenderNodes(view);
|
||||||
|
return {rootNodes, view};
|
||||||
|
}
|
||||||
|
|
||||||
|
class Service {
|
||||||
|
data: any;
|
||||||
|
}
|
||||||
|
|
||||||
|
INLINE_DYNAMIC_VALUES.forEach((inlineDynamic) => {
|
||||||
|
it(`should support pure arrays in ${InlineDynamic[inlineDynamic]} bindings`, () => {
|
||||||
|
let values: any[];
|
||||||
|
|
||||||
|
const {view, rootNodes} = createAndGetRootNodes(compViewDef(
|
||||||
|
[
|
||||||
|
elementDef(NodeFlags.None, 2, 'span'), pureArrayDef(2),
|
||||||
|
providerDef(NodeFlags.None, Service, [], {data: [0, 'data']})
|
||||||
|
],
|
||||||
|
(updater, view) => {
|
||||||
|
callUpdater(
|
||||||
|
updater, inlineDynamic, view, 2,
|
||||||
|
[callUpdater(updater, inlineDynamic, view, 1, values)]);
|
||||||
|
}));
|
||||||
|
const service = view.nodes[2].provider;
|
||||||
|
|
||||||
|
values = [1, 2];
|
||||||
|
checkAndUpdateView(view);
|
||||||
|
const arr0 = service.data;
|
||||||
|
expect(arr0).toEqual([1, 2]);
|
||||||
|
|
||||||
|
// instance should not change
|
||||||
|
// if the values don't change
|
||||||
|
checkAndUpdateView(view);
|
||||||
|
expect(service.data).toBe(arr0);
|
||||||
|
|
||||||
|
values = [3, 2];
|
||||||
|
checkAndUpdateView(view);
|
||||||
|
const arr1 = service.data;
|
||||||
|
expect(arr1).not.toBe(arr0);
|
||||||
|
expect(arr1).toEqual([3, 2]);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
INLINE_DYNAMIC_VALUES.forEach((inlineDynamic) => {
|
||||||
|
it(`should support pure objects in ${InlineDynamic[inlineDynamic]} bindings`, () => {
|
||||||
|
let values: any[];
|
||||||
|
|
||||||
|
const {view, rootNodes} = createAndGetRootNodes(compViewDef(
|
||||||
|
[
|
||||||
|
elementDef(NodeFlags.None, 2, 'span'), pureObjectDef(['a', 'b']),
|
||||||
|
providerDef(NodeFlags.None, Service, [], {data: [0, 'data']})
|
||||||
|
],
|
||||||
|
(updater, view) => {
|
||||||
|
callUpdater(
|
||||||
|
updater, inlineDynamic, view, 2,
|
||||||
|
[callUpdater(updater, inlineDynamic, view, 1, values)]);
|
||||||
|
}));
|
||||||
|
const service = view.nodes[2].provider;
|
||||||
|
|
||||||
|
values = [1, 2];
|
||||||
|
checkAndUpdateView(view);
|
||||||
|
const obj0 = service.data;
|
||||||
|
expect(obj0).toEqual({a: 1, b: 2});
|
||||||
|
|
||||||
|
// instance should not change
|
||||||
|
// if the values don't change
|
||||||
|
checkAndUpdateView(view);
|
||||||
|
expect(service.data).toBe(obj0);
|
||||||
|
|
||||||
|
values = [3, 2];
|
||||||
|
checkAndUpdateView(view);
|
||||||
|
const obj1 = service.data;
|
||||||
|
expect(obj1).not.toBe(obj0);
|
||||||
|
expect(obj1).toEqual({a: 3, b: 2});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
INLINE_DYNAMIC_VALUES.forEach((inlineDynamic) => {
|
||||||
|
it(`should support pure pipes in ${InlineDynamic[inlineDynamic]} bindings`, () => {
|
||||||
|
class SomePipe implements PipeTransform {
|
||||||
|
transform(v1: any, v2: any) { return [v1 + 10, v2 + 20]; }
|
||||||
|
}
|
||||||
|
|
||||||
|
let values: any[];
|
||||||
|
|
||||||
|
const {view, rootNodes} = createAndGetRootNodes(compViewDef(
|
||||||
|
[
|
||||||
|
elementDef(NodeFlags.None, 3, 'span'), providerDef(NodeFlags.None, SomePipe, []),
|
||||||
|
purePipeDef(SomePipe, 2),
|
||||||
|
providerDef(NodeFlags.None, Service, [], {data: [0, 'data']})
|
||||||
|
],
|
||||||
|
(updater, view) => {
|
||||||
|
callUpdater(
|
||||||
|
updater, inlineDynamic, view, 3,
|
||||||
|
[callUpdater(updater, inlineDynamic, view, 2, values)]);
|
||||||
|
}));
|
||||||
|
const service = view.nodes[3].provider;
|
||||||
|
|
||||||
|
values = [1, 2];
|
||||||
|
checkAndUpdateView(view);
|
||||||
|
const obj0 = service.data;
|
||||||
|
expect(obj0).toEqual([11, 22]);
|
||||||
|
|
||||||
|
// instance should not change
|
||||||
|
// if the values don't change
|
||||||
|
checkAndUpdateView(view);
|
||||||
|
expect(service.data).toBe(obj0);
|
||||||
|
|
||||||
|
values = [3, 2];
|
||||||
|
checkAndUpdateView(view);
|
||||||
|
const obj1 = service.data;
|
||||||
|
expect(obj1).not.toBe(obj0);
|
||||||
|
expect(obj1).toEqual([13, 22]);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
Loading…
Reference in New Issue