diff --git a/packages/core/src/render3/index.ts b/packages/core/src/render3/index.ts index c96fe0d43a..1b45cd487b 100644 --- a/packages/core/src/render3/index.ts +++ b/packages/core/src/render3/index.ts @@ -87,6 +87,7 @@ export { objectLiteral6 as o6, objectLiteral7 as o7, objectLiteral8 as o8, + objectLiteralV as oV, } from './object_literal'; diff --git a/packages/core/src/render3/instructions.ts b/packages/core/src/render3/instructions.ts index 3c586d0eec..f6bc468888 100644 --- a/packages/core/src/render3/instructions.ts +++ b/packages/core/src/render3/instructions.ts @@ -499,8 +499,7 @@ export function createTView(): TView { contentCheckHooks: null, viewHooks: null, viewCheckHooks: null, - destroyHooks: null, - objectLiterals: null + destroyHooks: null }; } @@ -1736,6 +1735,12 @@ function valueInData(data: any[], index: number, value?: T): T { return value !; } +/** Gets the binding at the current bindingIndex */ +export function peekBinding(): any { + ngDevMode && assertNotEqual(currentView.bindingStartIndex, null, 'bindingStartIndex'); + return data[bindingIndex]; +} + export function getCurrentQueries(QueryType: {new (): LQueries}): LQueries { return currentQueries || (currentQueries = new QueryType()); } @@ -1748,10 +1753,6 @@ export function getRenderer(): Renderer3 { return renderer; } -export function getTView(): TView { - return currentView.tView; -} - export function getDirectiveInstance(instanceOrArray: T | [T]): T { // Directives with content queries store an array in data[directiveIndex] // with the instance as the first index diff --git a/packages/core/src/render3/interfaces/view.ts b/packages/core/src/render3/interfaces/view.ts index e8bcca1285..d64a48922e 100644 --- a/packages/core/src/render3/interfaces/view.ts +++ b/packages/core/src/render3/interfaces/view.ts @@ -259,9 +259,6 @@ export interface TView { * Odd indices: Hook function */ destroyHooks: HookData|null; - - /** Contains copies of object literals that were passed as bindings in this view. */ - objectLiterals: any[]|null; } /** diff --git a/packages/core/src/render3/object_literal.ts b/packages/core/src/render3/object_literal.ts index be39f34117..79784cc1a6 100644 --- a/packages/core/src/render3/object_literal.ts +++ b/packages/core/src/render3/object_literal.ts @@ -6,268 +6,294 @@ * found in the LICENSE file at https://angular.io/license */ -import {assertEqual} from './assert'; -import {NO_CHANGE, bind, getTView} from './instructions'; - +import {NO_CHANGE, bind, peekBinding} from './instructions'; /** - * Updates an expression in an object or array literal if the expression has changed. - * Used in objectLiteral instructions. - * - * @param obj Object to update - * @param key Key to set in object - * @param exp Expression to set at key - * @returns Whether or not there has been a change - */ -function updateBinding(obj: any, key: string | number, exp: any): boolean { - if (bind(exp) !== NO_CHANGE) { - obj[key] = exp; - return true; - } - return false; -} - -/** Updates two expressions in an object or array literal if they have changed. */ -function updateBinding2( - obj: any, key1: string | number, exp1: any, key2: string | number, exp2: any): boolean { - let different = updateBinding(obj, key1, exp1); - return updateBinding(obj, key2, exp2) || different; -} - -/** Updates four expressions in an object or array literal if they have changed. */ -function updateBinding4( - obj: any, key1: string | number, exp1: any, key2: string | number, exp2: any, - key3: string | number, exp3: any, key4: string | number, exp4: any): boolean { - let different = updateBinding2(obj, key1, exp1, key2, exp2); - return updateBinding2(obj, key3, exp3, key4, exp4) || different; -} - -/** - * Gets a blueprint of an object or array if one has already been saved, or copies the - * object and saves it for the next change detection run if it hasn't. - */ -function getMutableBlueprint(index: number, obj: any): any { - const tView = getTView(); - const objectLiterals = tView.objectLiterals; - if (objectLiterals && index < objectLiterals.length) { - return objectLiterals[index]; - } else { - ngDevMode && objectLiterals && assertEqual(index, objectLiterals.length, 'index'); - return (objectLiterals || (tView.objectLiterals = []))[index] = copyObject(obj); - } -} - -/** Copies an object or array */ -function copyObject(obj: any): any { - return Array.isArray(obj) ? obj.slice() : {...obj}; -} - -/** - * Updates the expression in the given object or array if it has changed and returns a copy. + * If the object or array has changed, returns a copy with the updated expression. * Or if the expression hasn't changed, returns NO_CHANGE. * - * @param objIndex Index of object blueprint in objectLiterals - * @param obj Object to update - * @param key Key to set in object - * @param exp Expression to set at key - * @returns A copy of the object or NO_CHANGE + * @param factoryFn Function that returns an updated instance of the object/array + * @param exp Updated expression value + * @returns A copy of the object/array or NO_CHANGE */ -export function objectLiteral1(objIndex: number, obj: any, key: string | number, exp: any): any { - obj = getMutableBlueprint(objIndex, obj); - if (bind(exp) === NO_CHANGE) { - return NO_CHANGE; - } else { - obj[key] = exp; - // Must copy to change identity when binding changes - return copyObject(obj); - } +export function objectLiteral1(factoryFn: (v: any) => any, exp: any): any { + let different = false; + const latestValue = exp === NO_CHANGE ? peekBinding() : exp; + if (bind(exp) !== NO_CHANGE) different = true; + + return different ? factoryFn(latestValue) : NO_CHANGE; } /** - * Updates the expressions in the given object or array if they have changed and returns a copy. + * If the object or array has changed, returns a copy with all updated expressions. * Or if no expressions have changed, returns NO_CHANGE. * - * @param objIndex - * @param obj - * @param key1 + * @param factoryFn * @param exp1 - * @param key2 * @param exp2 - * @returns A copy of the array or NO_CHANGE + * @returns A copy of the object/array or NO_CHANGE */ -export function objectLiteral2( - objIndex: number, obj: any, key1: string | number, exp1: any, key2: string | number, - exp2: any): any { - obj = getMutableBlueprint(objIndex, obj); - return updateBinding2(obj, key1, exp1, key2, exp2) ? copyObject(obj) : NO_CHANGE; +export function objectLiteral2(factoryFn: (v1: any, v2: any) => any, exp1: any, exp2: any): any { + let different = false; + + const latestVal1 = exp1 === NO_CHANGE ? peekBinding() : exp1; + if (bind(exp1) !== NO_CHANGE) different = true; + + const latestVal2 = exp2 === NO_CHANGE ? peekBinding() : exp2; + if (bind(exp2) !== NO_CHANGE) different = true; + + return different ? factoryFn(latestVal1, latestVal2) : NO_CHANGE; } /** - * Updates the expressions in the given object or array if they have changed and returns a copy. + * If the object or array has changed, returns a copy with all updated expressions. * Or if no expressions have changed, returns NO_CHANGE. * - * @param objIndex - * @param obj - * @param key1 + * @param factoryFn * @param exp1 - * @param key2 * @param exp2 - * @param key3 * @param exp3 - * @returns A copy of the object or NO_CHANGE + * @returns A copy of the object/array or NO_CHANGE */ export function objectLiteral3( - objIndex: number, obj: any, key1: string | number, exp1: any, key2: string | number, exp2: any, - key3: string | number, exp3: any): any { - obj = getMutableBlueprint(objIndex, obj); - let different = updateBinding2(obj, key1, exp1, key2, exp2); - return updateBinding(obj, key3, exp3) || different ? copyObject(obj) : NO_CHANGE; + factoryFn: (v1: any, v2: any, v3: any) => any, exp1: any, exp2: any, exp3: any): any { + let different = false; + + const latestVal1 = exp1 === NO_CHANGE ? peekBinding() : exp1; + if (bind(exp1) !== NO_CHANGE) different = true; + + const latestVal2 = exp2 === NO_CHANGE ? peekBinding() : exp2; + if (bind(exp2) !== NO_CHANGE) different = true; + + const latestVal3 = exp3 === NO_CHANGE ? peekBinding() : exp3; + if (bind(exp3) !== NO_CHANGE) different = true; + + return different ? factoryFn(latestVal1, latestVal2, latestVal3) : NO_CHANGE; } /** - * Updates the expressions in the given object or array if they have changed and returns a copy. + * If the object or array has changed, returns a copy with all updated expressions. * Or if no expressions have changed, returns NO_CHANGE. * - * @param objIndex - * @param obj - * @param key1 + * @param factoryFn * @param exp1 - * @param key2 * @param exp2 - * @param key3 * @param exp3 - * @param key4 * @param exp4 - * @returns A copy of the object or NO_CHANGE + * @returns A copy of the object/array or NO_CHANGE */ export function objectLiteral4( - objIndex: number, obj: any, key1: string | number, exp1: any, key2: string | number, exp2: any, - key3: string | number, exp3: any, key4: string | number, exp4: any): any { - obj = getMutableBlueprint(objIndex, obj); - return updateBinding4(obj, key1, exp1, key2, exp2, key3, exp3, key4, exp4) ? copyObject(obj) : - NO_CHANGE; + factoryFn: (v1: any, v2: any, v3: any, v4: any) => any, exp1: any, exp2: any, exp3: any, + exp4: any): any { + let different = false; + + const latestVal1 = exp1 === NO_CHANGE ? peekBinding() : exp1; + if (bind(exp1) !== NO_CHANGE) different = true; + + const latestVal2 = exp2 === NO_CHANGE ? peekBinding() : exp2; + if (bind(exp2) !== NO_CHANGE) different = true; + + const latestVal3 = exp3 === NO_CHANGE ? peekBinding() : exp3; + if (bind(exp3) !== NO_CHANGE) different = true; + + const latestVal4 = exp4 === NO_CHANGE ? peekBinding() : exp4; + if (bind(exp4) !== NO_CHANGE) different = true; + + return different ? factoryFn(latestVal1, latestVal2, latestVal3, latestVal4) : NO_CHANGE; } /** - * Updates the expressions in the given object or array if they have changed and returns a copy. + * If the object or array has changed, returns a copy with all updated expressions. * Or if no expressions have changed, returns NO_CHANGE. * - * @param objIndex - * @param obj - * @param key1 + * @param factoryFn * @param exp1 - * @param key2 * @param exp2 - * @param key3 * @param exp3 - * @param key4 * @param exp4 - * @param key5 * @param exp5 - * @returns A copy of the object or NO_CHANGE + * @returns A copy of the object/array or NO_CHANGE */ export function objectLiteral5( - objIndex: number, obj: any, key1: string | number, exp1: any, key2: string | number, exp2: any, - key3: string | number, exp3: any, key4: string | number, exp4: any, key5: string | number, - exp5: any): any { - obj = getMutableBlueprint(objIndex, obj); - let different = updateBinding4(obj, key1, exp1, key2, exp2, key3, exp3, key4, exp4); - return updateBinding(obj, key5, exp5) || different ? copyObject(obj) : NO_CHANGE; + factoryFn: (v1: any, v2: any, v3: any, v4: any, v5: any) => any, exp1: any, exp2: any, + exp3: any, exp4: any, exp5: any): any { + let different = false; + + const latestVal1 = exp1 === NO_CHANGE ? peekBinding() : exp1; + if (bind(exp1) !== NO_CHANGE) different = true; + + const latestVal2 = exp2 === NO_CHANGE ? peekBinding() : exp2; + if (bind(exp2) !== NO_CHANGE) different = true; + + const latestVal3 = exp3 === NO_CHANGE ? peekBinding() : exp3; + if (bind(exp3) !== NO_CHANGE) different = true; + + const latestVal4 = exp4 === NO_CHANGE ? peekBinding() : exp4; + if (bind(exp4) !== NO_CHANGE) different = true; + + const latestVal5 = exp5 === NO_CHANGE ? peekBinding() : exp5; + if (bind(exp5) !== NO_CHANGE) different = true; + + return different ? factoryFn(latestVal1, latestVal2, latestVal3, latestVal4, latestVal5) : + NO_CHANGE; } /** - * Updates the expressions in the given object or array if they have changed and returns a copy. + * If the object or array has changed, returns a copy with all updated expressions. * Or if no expressions have changed, returns NO_CHANGE. * - * @param objIndex - * @param obj - * @param key1 + * @param factoryFn * @param exp1 - * @param key2 * @param exp2 - * @param key3 * @param exp3 - * @param key4 * @param exp4 - * @param key5 * @param exp5 - * @param key6 * @param exp6 - * @returns A copy of the object or NO_CHANGE + * @returns A copy of the object/array or NO_CHANGE */ export function objectLiteral6( - objIndex: number, obj: any, key1: string | number, exp1: any, key2: string | number, exp2: any, - key3: string | number, exp3: any, key4: string | number, exp4: any, key5: string | number, - exp5: any, key6: string | number, exp6: any): any { - obj = getMutableBlueprint(objIndex, obj); - let different = updateBinding4(obj, key1, exp1, key2, exp2, key3, exp3, key4, exp4); - return updateBinding2(obj, key5, exp5, key6, exp6) || different ? copyObject(obj) : NO_CHANGE; -} + factoryFn: (v1: any, v2: any, v3: any, v4: any, v5: any, v6: any) => any, exp1: any, exp2: any, + exp3: any, exp4: any, exp5: any, exp6: any): any { + let different = false; -/** - * Updates the expressions in the given object or array if they have changed and returns a copy. - * Or if no expressions have changed, returns NO_CHANGE. - * - * @param objIndex - * @param obj - * @param key1 - * @param exp1 - * @param key2 - * @param exp2 - * @param key3 - * @param exp3 - * @param key4 - * @param exp4 - * @param key5 - * @param exp5 - * @param key6 - * @param exp6 - * @param key7 - * @param exp7 - * @returns A copy of the object or NO_CHANGE - */ -export function objectLiteral7( - objIndex: number, obj: any, key1: string | number, exp1: any, key2: string | number, exp2: any, - key3: string | number, exp3: any, key4: string | number, exp4: any, key5: string | number, - exp5: any, key6: string | number, exp6: any, key7: string | number, exp7: any): any { - obj = getMutableBlueprint(objIndex, obj); - let different = updateBinding4(obj, key1, exp1, key2, exp2, key3, exp3, key4, exp4); - different = updateBinding2(obj, key5, exp5, key6, exp6) || different; - return updateBinding(obj, key7, exp7) || different ? copyObject(obj) : NO_CHANGE; -} + const latestVal1 = exp1 === NO_CHANGE ? peekBinding() : exp1; + if (bind(exp1) !== NO_CHANGE) different = true; -/** - * Updates the expressions in the given object or array if they have changed and returns a copy. - * Or if no expressions have changed, returns NO_CHANGE. - * - * @param objIndex - * @param obj - * @param key1 - * @param exp1 - * @param key2 - * @param exp2 - * @param key3 - * @param exp3 - * @param key4 - * @param exp4 - * @param key5 - * @param exp5 - * @param key6 - * @param exp6 - * @param key7 - * @param exp7 - * @param key8 - * @param exp8 - * @returns A copy of the object or NO_CHANGE - */ -export function objectLiteral8( - objIndex: number, obj: any, key1: string | number, exp1: any, key2: string | number, exp2: any, - key3: string | number, exp3: any, key4: string | number, exp4: any, key5: string | number, - exp5: any, key6: string | number, exp6: any, key7: string | number, exp7: any, - key8: string | number, exp8: any): any { - obj = getMutableBlueprint(objIndex, obj); - let different = updateBinding4(obj, key1, exp1, key2, exp2, key3, exp3, key4, exp4); - return updateBinding4(obj, key5, exp5, key6, exp6, key7, exp7, key8, exp8) || different ? - copyObject(obj) : + const latestVal2 = exp2 === NO_CHANGE ? peekBinding() : exp2; + if (bind(exp2) !== NO_CHANGE) different = true; + + const latestVal3 = exp3 === NO_CHANGE ? peekBinding() : exp3; + if (bind(exp3) !== NO_CHANGE) different = true; + + const latestVal4 = exp4 === NO_CHANGE ? peekBinding() : exp4; + if (bind(exp4) !== NO_CHANGE) different = true; + + const latestVal5 = exp5 === NO_CHANGE ? peekBinding() : exp5; + if (bind(exp5) !== NO_CHANGE) different = true; + + const latestVal6 = exp6 === NO_CHANGE ? peekBinding() : exp6; + if (bind(exp6) !== NO_CHANGE) different = true; + + return different ? + factoryFn(latestVal1, latestVal2, latestVal3, latestVal4, latestVal5, latestVal6) : NO_CHANGE; } + +/** + * If the object or array has changed, returns a copy with all updated expressions. + * Or if no expressions have changed, returns NO_CHANGE. + * + * @param factoryFn + * @param exp1 + * @param exp2 + * @param exp3 + * @param exp4 + * @param exp5 + * @param exp6 + * @param exp7 + * @returns A copy of the object/array or NO_CHANGE + */ +export function objectLiteral7( + factoryFn: (v1: any, v2: any, v3: any, v4: any, v5: any, v6: any, v7: any) => any, exp1: any, + exp2: any, exp3: any, exp4: any, exp5: any, exp6: any, exp7: any): any { + let different = false; + + const latestVal1 = exp1 === NO_CHANGE ? peekBinding() : exp1; + if (bind(exp1) !== NO_CHANGE) different = true; + + const latestVal2 = exp2 === NO_CHANGE ? peekBinding() : exp2; + if (bind(exp2) !== NO_CHANGE) different = true; + + const latestVal3 = exp3 === NO_CHANGE ? peekBinding() : exp3; + if (bind(exp3) !== NO_CHANGE) different = true; + + const latestVal4 = exp4 === NO_CHANGE ? peekBinding() : exp4; + if (bind(exp4) !== NO_CHANGE) different = true; + + const latestVal5 = exp5 === NO_CHANGE ? peekBinding() : exp5; + if (bind(exp5) !== NO_CHANGE) different = true; + + const latestVal6 = exp6 === NO_CHANGE ? peekBinding() : exp6; + if (bind(exp6) !== NO_CHANGE) different = true; + + const latestVal7 = exp7 === NO_CHANGE ? peekBinding() : exp7; + if (bind(exp7) !== NO_CHANGE) different = true; + + return different ? + factoryFn( + latestVal1, latestVal2, latestVal3, latestVal4, latestVal5, latestVal6, latestVal7) : + NO_CHANGE; +} + +/** + * If the object or array has changed, returns a copy with all updated expressions. + * Or if no expressions have changed, returns NO_CHANGE. + * + * @param factoryFn + * @param exp1 + * @param exp2 + * @param exp3 + * @param exp4 + * @param exp5 + * @param exp6 + * @param exp7 + * @param exp8 + * @returns A copy of the object/array or NO_CHANGE + */ +export function objectLiteral8( + factoryFn: (v1: any, v2: any, v3: any, v4: any, v5: any, v6: any, v7: any, v8: any) => any, + exp1: any, exp2: any, exp3: any, exp4: any, exp5: any, exp6: any, exp7: any, exp8: any): any { + let different = false; + + const latestVal1 = exp1 === NO_CHANGE ? peekBinding() : exp1; + if (bind(exp1) !== NO_CHANGE) different = true; + + const latestVal2 = exp2 === NO_CHANGE ? peekBinding() : exp2; + if (bind(exp2) !== NO_CHANGE) different = true; + + const latestVal3 = exp3 === NO_CHANGE ? peekBinding() : exp3; + if (bind(exp3) !== NO_CHANGE) different = true; + + const latestVal4 = exp4 === NO_CHANGE ? peekBinding() : exp4; + if (bind(exp4) !== NO_CHANGE) different = true; + + const latestVal5 = exp5 === NO_CHANGE ? peekBinding() : exp5; + if (bind(exp5) !== NO_CHANGE) different = true; + + const latestVal6 = exp6 === NO_CHANGE ? peekBinding() : exp6; + if (bind(exp6) !== NO_CHANGE) different = true; + + const latestVal7 = exp7 === NO_CHANGE ? peekBinding() : exp7; + if (bind(exp7) !== NO_CHANGE) different = true; + + const latestVal8 = exp8 === NO_CHANGE ? peekBinding() : exp8; + if (bind(exp8) !== NO_CHANGE) different = true; + + return different ? factoryFn( + latestVal1, latestVal2, latestVal3, latestVal4, latestVal5, latestVal6, + latestVal7, latestVal8) : + NO_CHANGE; +} + +/** + * objectLiteral instruction that can support any number of bindings. + * + * If the object or array has changed, returns a copy with all updated expressions. + * Or if no expressions have changed, returns NO_CHANGE. + * + * @param factoryFn A factory function that takes binding values and builds an object or array + * containing those values. + * @param exp An array of binding values + * @returns A copy of the object/array or NO_CHANGE + */ +export function objectLiteralV(factoryFn: (v: any[]) => any, exps: any[]): any { + let different = false; + + for (let i = 0; i < exps.length; i++) { + const exp = exps[i]; + if (exp === NO_CHANGE) exps[i] = peekBinding(); + if (bind(exp) !== NO_CHANGE) different = true; + } + + return different ? factoryFn(exps) : NO_CHANGE; +} diff --git a/packages/core/test/render3/compiler_canonical_spec.ts b/packages/core/test/render3/compiler_canonical_spec.ts index 5c93e2474a..b9be4fc448 100644 --- a/packages/core/test/render3/compiler_canonical_spec.ts +++ b/packages/core/test/render3/compiler_canonical_spec.ts @@ -226,7 +226,7 @@ describe('compiler specification', () => { r3.E(0, MyComp); r3.e(); } - r3.p(0, 'names', r3.o1(0, e0_literal, 1, ctx.customName)); + r3.p(0, 'names', r3.o1(e0_ff, ctx.customName)); MyComp.ngComponentDef.h(1, 0); r3.r(1, 0); } @@ -235,18 +235,118 @@ describe('compiler specification', () => { } // NORMATIVE - const e0_literal = ['Nancy', null]; + const e0_ff = (v: any) => ['Nancy', v]; // /NORMATIVE expect(renderComp(MyApp)).toEqual(`

Nancy

Bess

`); - expect(e0_literal).toEqual(['Nancy', null]); + }); + + it('should support 9+ bindings in array literals', () => { + @Component({ + selector: 'my-comp', + template: ` + {{ names[0] }} + {{ names[1] }} + {{ names[3] }} + {{ names[4] }} + {{ names[5] }} + {{ names[6] }} + {{ names[7] }} + {{ names[8] }} + {{ names[9] }} + {{ names[10] }} + {{ names[11] }} + ` + }) + class MyComp { + @Input() names: string[]; + + static ngComponentDef = r3.defineComponent({ + type: MyComp, + tag: 'my-comp', + factory: function MyComp_Factory() { return new MyComp(); }, + template: function MyComp_Template(ctx: MyComp, cm: boolean) { + if (cm) { + r3.T(0); + r3.T(1); + r3.T(2); + r3.T(3); + r3.T(4); + r3.T(5); + r3.T(6); + r3.T(7); + r3.T(8); + r3.T(9); + r3.T(10); + r3.T(11); + } + r3.t(0, r3.b(ctx.names[0])); + r3.t(1, r3.b(ctx.names[1])); + r3.t(2, r3.b(ctx.names[2])); + r3.t(3, r3.b(ctx.names[3])); + r3.t(4, r3.b(ctx.names[4])); + r3.t(5, r3.b(ctx.names[5])); + r3.t(6, r3.b(ctx.names[6])); + r3.t(7, r3.b(ctx.names[7])); + r3.t(8, r3.b(ctx.names[8])); + r3.t(9, r3.b(ctx.names[9])); + r3.t(10, r3.b(ctx.names[10])); + r3.t(11, r3.b(ctx.names[11])); + }, + inputs: {names: 'names'} + }); + } + + @Component({ + selector: 'my-app', + template: ` + + + ` + }) + class MyApp { + n0 = 'a'; + n1 = 'b'; + n2 = 'c'; + n3 = 'd'; + n4 = 'e'; + n5 = 'f'; + n6 = 'g'; + n7 = 'h'; + n8 = 'i'; + + // NORMATIVE + static ngComponentDef = r3.defineComponent({ + type: MyApp, + tag: 'my-app', + factory: function MyApp_Factory() { return new MyApp(); }, + template: function MyApp_Template(c: MyApp, cm: boolean) { + if (cm) { + r3.E(0, MyComp); + r3.e(); + } + r3.p( + 0, 'names', r3.oV(e0_ff, [c.n0, c.n1, c.n2, c.n3, c.n4, c.n5, c.n6, c.n7, c.n8])); + MyComp.ngComponentDef.h(1, 0); + r3.r(1, 0); + } + }); + // /NORMATIVE + } + + // NORMATIVE + const e0_ff = (v: any[]) => + ['start-', v[0], v[1], v[2], v[3], v[4], '-middle-', v[5], v[6], v[7], v[8], '-end']; + // /NORMATIVE + + expect(renderComp(MyApp)).toEqual(`start-abcde-middle-fghi-end`); }); it('should support object literals', () => { @Component({ selector: 'object-comp', template: ` -

{{ config.duration }}

+

{{ config['duration'] }}

{{ config.animation }}

` }) @@ -266,7 +366,7 @@ describe('compiler specification', () => { r3.T(3); r3.e(); } - r3.t(1, r3.b(ctx.config.duration)); + r3.t(1, r3.b(ctx.config['duration'])); r3.t(3, r3.b(ctx.config.animation)); }, inputs: {config: 'config'} @@ -276,7 +376,7 @@ describe('compiler specification', () => { @Component({ selector: 'my-app', template: ` - + ` }) class MyApp { @@ -292,7 +392,7 @@ describe('compiler specification', () => { r3.E(0, ObjectComp); r3.e(); } - r3.p(0, 'config', r3.o1(0, e0_literal, 'animation', ctx.name)); + r3.p(0, 'config', r3.o1(e0_ff, ctx.name)); ObjectComp.ngComponentDef.h(1, 0); r3.r(1, 0); } @@ -301,11 +401,10 @@ describe('compiler specification', () => { } // NORMATIVE - const e0_literal = {duration: 500, animation: null}; + const e0_ff = (v: any) => { return {'duration': 500, animation: v}; }; // /NORMATIVE expect(renderComp(MyApp)).toEqual(`

500

slide

`); - expect(e0_literal).toEqual({duration: 500, animation: null}); }); it('should support expressions nested deeply in object/array literals', () => { @@ -367,9 +466,7 @@ describe('compiler specification', () => { } r3.p( 0, 'config', - r3.o2( - 2, e0_literal_2, 'animation', ctx.name, 'actions', - r3.o1(1, e0_literal_1, 1, r3.o1(0, e0_literal, 'duration', ctx.duration)))); + r3.o2(e0_ff_2, ctx.name, r3.o1(e0_ff_1, r3.o1(e0_ff, ctx.duration)))); NestedComp.ngComponentDef.h(1, 0); r3.r(1, 0); } @@ -378,10 +475,10 @@ describe('compiler specification', () => { } // NORMATIVE - const e0_literal = {opacity: 1, duration: null}; + const e0_ff = (v: any) => { return {opacity: 1, duration: v}; }; const c0 = {opacity: 0, duration: 0}; - const e0_literal_1 = [c0, null]; - const e0_literal_2 = {animation: null, actions: null}; + const e0_ff_1 = (v: any) => [c0, v]; + const e0_ff_2 = (v1: any, v2: any) => { return {animation: v1, actions: v2}; }; // /NORMATIVE expect(renderComp(MyApp)) diff --git a/packages/core/test/render3/object_literal_spec.ts b/packages/core/test/render3/object_literal_spec.ts index 941c79e459..f5b50d4882 100644 --- a/packages/core/test/render3/object_literal_spec.ts +++ b/packages/core/test/render3/object_literal_spec.ts @@ -6,8 +6,8 @@ * found in the LICENSE file at https://angular.io/license */ import {defineComponent} from '../../src/render3/index'; -import {componentRefresh, elementEnd, elementProperty, elementStart, memory} from '../../src/render3/instructions'; -import {objectLiteral1, objectLiteral2, objectLiteral3, objectLiteral4, objectLiteral5, objectLiteral6, objectLiteral7, objectLiteral8} from '../../src/render3/object_literal'; +import {componentRefresh, container, containerRefreshEnd, containerRefreshStart, elementEnd, elementProperty, elementStart, embeddedViewEnd, embeddedViewStart, memory} from '../../src/render3/instructions'; +import {objectLiteral1, objectLiteral2, objectLiteral3, objectLiteral4, objectLiteral5, objectLiteral6, objectLiteral7, objectLiteral8, objectLiteralV} from '../../src/render3/object_literal'; import {renderToHtml} from '../../test/render3/render_util'; describe('array literals', () => { @@ -32,12 +32,12 @@ describe('array literals', () => { elementStart(0, MyComp); elementEnd(); } - elementProperty(0, 'names', objectLiteral1(0, e0_literal, 1, ctx.customName)); + elementProperty(0, 'names', objectLiteral1(e0_ff, ctx.customName)); MyComp.ngComponentDef.h(1, 0); componentRefresh(1, 0); } - const e0_literal = ['Nancy', null, 'Bess']; + const e0_ff = (v: any) => ['Nancy', v, 'Bess']; renderToHtml(Template, {customName: 'Carson'}); const firstArray = myComp !.names; @@ -52,8 +52,6 @@ describe('array literals', () => { // Identity must change if binding changes expect(firstArray).not.toBe(myComp !.names); - - expect(e0_literal).toEqual(['Nancy', null, 'Bess']); }); it('should support multiple array literals passed through to one node', () => { @@ -72,21 +70,23 @@ describe('array literals', () => { }); } - /** + /** + * + * */ function Template(ctx: any, cm: boolean) { if (cm) { elementStart(0, ManyPropComp); elementEnd(); } - elementProperty(0, 'names1', objectLiteral1(0, e0_literal, 1, ctx.customName)); - elementProperty(0, 'names2', objectLiteral1(1, e0_literal_1, 0, ctx.customName2)); + elementProperty(0, 'names1', objectLiteral1(e0_ff, ctx.customName)); + elementProperty(0, 'names2', objectLiteral1(e0_ff_1, ctx.customName2)); ManyPropComp.ngComponentDef.h(1, 0); componentRefresh(1, 0); } - const e0_literal = ['Nancy', null]; - const e0_literal_1 = [null]; + const e0_ff = (v: any) => ['Nancy', v]; + const e0_ff_1 = (v: any) => [v]; renderToHtml(Template, {customName: 'Carson', customName2: 'George'}); expect(manyPropComp !.names1).toEqual(['Nancy', 'Carson']); @@ -95,10 +95,6 @@ describe('array literals', () => { renderToHtml(Template, {customName: 'George', customName2: 'Carson'}); expect(manyPropComp !.names1).toEqual(['Nancy', 'George']); expect(manyPropComp !.names2).toEqual(['Carson']); - - expect(e0_literal).toEqual(['Nancy', null]); - expect(e0_literal_1).toEqual([null]); - }); @@ -109,13 +105,12 @@ describe('array literals', () => { elementStart(0, MyComp); elementEnd(); } - elementProperty( - 0, 'names', objectLiteral2(0, e0_literal, 1, ctx.customName, 3, ctx.customName2)); + elementProperty(0, 'names', objectLiteral2(e0_ff, ctx.customName, ctx.customName2)); MyComp.ngComponentDef.h(1, 0); componentRefresh(1, 0); } - const e0_literal = ['Nancy', null, 'Bess', null]; + const e0_ff = (v1: any, v2: any) => ['Nancy', v1, 'Bess', v2]; renderToHtml(Template, {customName: 'Carson', customName2: 'Hannah'}); const firstArray = myComp !.names; @@ -162,42 +157,40 @@ describe('array literals', () => { o8Comp = memory(11); elementEnd(); } - elementProperty(0, 'names', objectLiteral3(0, e0_literal, 5, c[5], 6, c[6], 7, c[7])); + elementProperty(0, 'names', objectLiteral3(e0_ff, c[5], c[6], c[7])); + elementProperty(2, 'names', objectLiteral4(e2_ff, c[4], c[5], c[6], c[7])); + elementProperty(4, 'names', objectLiteral5(e4_ff, c[3], c[4], c[5], c[6], c[7])); + elementProperty(6, 'names', objectLiteral6(e6_ff, c[2], c[3], c[4], c[5], c[6], c[7])); + elementProperty(8, 'names', objectLiteral7(e8_ff, c[1], c[2], c[3], c[4], c[5], c[6], c[7])); elementProperty( - 2, 'names', objectLiteral4(1, e2_literal, 4, c[4], 5, c[5], 6, c[6], 7, c[7])); - elementProperty( - 4, 'names', objectLiteral5(2, e4_literal, 3, c[3], 4, c[4], 5, c[5], 6, c[6], 7, c[7])); - elementProperty( - 6, 'names', - objectLiteral6(3, e6_literal, 2, c[2], 3, c[3], 4, c[4], 5, c[5], 6, c[6], 7, c[7])); - elementProperty( - 8, 'names', - objectLiteral7( - 4, e8_literal, 1, c[1], 2, c[2], 3, c[3], 4, c[4], 5, c[5], 6, c[6], 7, c[7])); - elementProperty( - 10, 'names', objectLiteral8( - 5, e10_literal, 0, c[0], 1, c[1], 2, c[2], 3, c[3], 4, c[4], 5, c[5], 6, - c[6], 7, c[7])); + 10, 'names', objectLiteral8(e10_ff, c[0], c[1], c[2], c[3], c[4], c[5], c[6], c[7])); MyComp.ngComponentDef.h(1, 0); - componentRefresh(1, 0); MyComp.ngComponentDef.h(3, 2); - componentRefresh(3, 2); MyComp.ngComponentDef.h(5, 4); - componentRefresh(5, 4); MyComp.ngComponentDef.h(7, 6); - componentRefresh(7, 6); MyComp.ngComponentDef.h(9, 8); - componentRefresh(9, 8); MyComp.ngComponentDef.h(11, 10); + componentRefresh(1, 0); + componentRefresh(3, 2); + componentRefresh(5, 4); + componentRefresh(7, 6); + componentRefresh(9, 8); componentRefresh(11, 10); } - const e0_literal = ['a', 'b', 'c', 'd', 'e', null, null, null]; - const e2_literal = ['a', 'b', 'c', 'd', null, null, null, null]; - const e4_literal = ['a', 'b', 'c', null, null, null, null, null]; - const e6_literal = ['a', 'b', null, null, null, null, null, null]; - const e8_literal = ['a', null, null, null, null, null, null, null]; - const e10_literal = [null, null, null, null, null, null, null, null]; + const e0_ff = (v1: any, v2: any, v3: any) => ['a', 'b', 'c', 'd', 'e', v1, v2, v3]; + const e2_ff = (v1: any, v2: any, v3: any, v4: any) => ['a', 'b', 'c', 'd', v1, v2, v3, v4]; + const e4_ff = + (v1: any, v2: any, v3: any, v4: any, v5: any) => ['a', 'b', 'c', v1, v2, v3, v4, v5]; + const e6_ff = + (v1: any, v2: any, v3: any, v4: any, v5: any, + v6: any) => ['a', 'b', v1, v2, v3, v4, v5, v6]; + const e8_ff = + (v1: any, v2: any, v3: any, v4: any, v5: any, v6: any, + v7: any) => ['a', v1, v2, v3, v4, v5, v6, v7]; + const e10_ff = + (v1: any, v2: any, v3: any, v4: any, v5: any, v6: any, v7: any, + v8: any) => [v1, v2, v3, v4, v5, v6, v7, v8]; renderToHtml(Template, ['a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i']); expect(o3Comp !.names).toEqual(['a', 'b', 'c', 'd', 'e', 'f', 'g', 'h']); @@ -216,33 +209,74 @@ describe('array literals', () => { expect(o8Comp !.names).toEqual(['a1', 'b1', 'c1', 'd1', 'e1', 'f1', 'g1', 'h1']); }); - it('should support an object literal', () => { - let objectComp: ObjectComp; - - class ObjectComp { - config: {[key: string]: any}; - - static ngComponentDef = defineComponent({ - type: ObjectComp, - tag: 'object-comp', - factory: function ObjectComp_Factory() { return objectComp = new ObjectComp(); }, - template: function ObjectComp_Template(ctx: ObjectComp, cm: boolean) {}, - inputs: {config: 'config'} - }); + it('should work with objectLiteralV for 9+ bindings', () => { + /** + * + * + */ + function Template(c: any, cm: boolean) { + if (cm) { + elementStart(0, MyComp); + elementEnd(); + } + elementProperty( + 0, 'names', objectLiteralV(e0_ff, [ + c[0], c[1], c[2], c[3], objectLiteral1(e0_ff_1, c[4]), c[5], c[6], c[7], c[8] + ])); + MyComp.ngComponentDef.h(1, 0); + componentRefresh(1, 0); } - /** */ + const e0_ff = + (v: any[]) => ['start', v[0], v[1], v[2], v[3], v[4], v[5], v[6], v[7], v[8], 'end']; + const e0_ff_1 = (v: any) => { return {name: v}; }; + + renderToHtml(Template, ['a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i']); + expect(myComp !.names).toEqual([ + 'start', 'a', 'b', 'c', 'd', {name: 'e'}, 'f', 'g', 'h', 'i', 'end' + ]); + + renderToHtml(Template, ['a1', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i']); + expect(myComp !.names).toEqual([ + 'start', 'a1', 'b', 'c', 'd', {name: 'e'}, 'f', 'g', 'h', 'i', 'end' + ]); + + renderToHtml(Template, ['a1', 'b', 'c', 'd', 'e5', 'f', 'g', 'h', 'i']); + expect(myComp !.names).toEqual([ + 'start', 'a1', 'b', 'c', 'd', {name: 'e5'}, 'f', 'g', 'h', 'i', 'end' + ]); + }); + +}); +describe('object literals', () => { + let objectComp: ObjectComp; + + class ObjectComp { + config: {[key: string]: any}; + + static ngComponentDef = defineComponent({ + type: ObjectComp, + tag: 'object-comp', + factory: function ObjectComp_Factory() { return objectComp = new ObjectComp(); }, + template: function ObjectComp_Template(ctx: ObjectComp, cm: boolean) {}, + inputs: {config: 'config'} + }); + } + + it('should support an object literal', () => { + + /** */ function Template(ctx: any, cm: boolean) { if (cm) { elementStart(0, ObjectComp); elementEnd(); } - elementProperty(0, 'config', objectLiteral1(0, e0_literal, 'animation', ctx.name)); + elementProperty(0, 'config', objectLiteral1(e0_ff, ctx.name)); ObjectComp.ngComponentDef.h(1, 0); componentRefresh(1, 0); } - const e0_literal = {duration: 500, animation: null}; + const e0_ff = (v: any) => { return {duration: 500, animation: v}; }; renderToHtml(Template, {name: 'slide'}); const firstObj = objectComp !.config; @@ -257,80 +291,107 @@ describe('array literals', () => { // Identity must change if binding changes expect(firstObj).not.toBe(objectComp !.config); - - expect(e0_literal).toEqual({duration: 500, animation: null}); }); it('should support expressions nested deeply in object/array literals', () => { - let nestedComp: NestedComp; - - class NestedComp { - config: {[key: string]: any}; - - static ngComponentDef = defineComponent({ - type: NestedComp, - tag: 'nested-comp', - factory: function NestedComp_Factory() { return nestedComp = new NestedComp(); }, - template: function NestedComp_Template(ctx: NestedComp, cm: boolean) {}, - inputs: {config: 'config'} - }); - } - /** - * - * + * */ function Template(ctx: any, cm: boolean) { if (cm) { - elementStart(0, NestedComp); + elementStart(0, ObjectComp); elementEnd(); } elementProperty( 0, 'config', objectLiteral2( - 2, e0_literal_2, 'animation', ctx.name, 'actions', - objectLiteral1( - 1, e0_literal_1, 1, objectLiteral1(0, e0_literal, 'duration', ctx.duration)))); - NestedComp.ngComponentDef.h(1, 0); + e0_ff, ctx.name, objectLiteral1(e0_ff_1, objectLiteral1(e0_ff_2, ctx.duration)))); + ObjectComp.ngComponentDef.h(1, 0); componentRefresh(1, 0); } - const e0_literal = {opacity: 1, duration: null}; - const e0_literal_1 = [{opacity: 0, duration: 0}, null]; - const e0_literal_2 = {animation: null, actions: null}; + const e0_ff = (v1: any, v2: any) => { return {animation: v1, actions: v2}; }; + const e0_ff_1 = (v: any) => [{opacity: 0, duration: 0}, v]; + const e0_ff_2 = (v: any) => { return {opacity: 1, duration: v}; }; renderToHtml(Template, {name: 'slide', duration: 100}); - expect(nestedComp !.config).toEqual({ + expect(objectComp !.config).toEqual({ animation: 'slide', actions: [{opacity: 0, duration: 0}, {opacity: 1, duration: 100}] }); - const firstConfig = nestedComp !.config; + const firstConfig = objectComp !.config; renderToHtml(Template, {name: 'slide', duration: 100}); - expect(nestedComp !.config).toEqual({ + expect(objectComp !.config).toEqual({ animation: 'slide', actions: [{opacity: 0, duration: 0}, {opacity: 1, duration: 100}] }); - expect(nestedComp !.config).toBe(firstConfig); + expect(objectComp !.config).toBe(firstConfig); renderToHtml(Template, {name: 'slide', duration: 50}); - expect(nestedComp !.config).toEqual({ + expect(objectComp !.config).toEqual({ animation: 'slide', actions: [{opacity: 0, duration: 0}, {opacity: 1, duration: 50}] }); - expect(nestedComp !.config).not.toBe(firstConfig); + expect(objectComp !.config).not.toBe(firstConfig); renderToHtml(Template, {name: 'tap', duration: 50}); - expect(nestedComp !.config).toEqual({ + expect(objectComp !.config).toEqual({ animation: 'tap', actions: [{opacity: 0, duration: 0}, {opacity: 1, duration: 50}] }); - expect(e0_literal).toEqual({opacity: 1, duration: null}); - expect(e0_literal_1).toEqual([{opacity: 0, duration: 0}, null]); - expect(e0_literal_2).toEqual({animation: null, actions: null}); + renderToHtml(Template, {name: 'drag', duration: 500}); + expect(objectComp !.config).toEqual({ + animation: 'drag', + actions: [{opacity: 0, duration: 0}, {opacity: 1, duration: 500}] + }); }); + it('should support multiple view instances with multiple bindings', () => { + let objectComps: ObjectComp[] = []; + + /** + * % for(let i = 0; i < 2; i++) { + * + * + * % } + */ + function Template(ctx: any, cm: boolean) { + if (cm) { + container(0); + } + containerRefreshStart(0); + { + for (let i = 0; i < 2; i++) { + if (embeddedViewStart(0)) { + elementStart(0, ObjectComp); + objectComps.push(memory(1)); + elementEnd(); + } + elementProperty( + 0, 'config', objectLiteral2(e0_ff, ctx.configs[i].opacity, ctx.configs[i].duration)); + ObjectComp.ngComponentDef.h(1, 0); + componentRefresh(1, 0); + embeddedViewEnd(); + } + } + containerRefreshEnd(); + } + + const e0_ff = (v1: any, v2: any) => { return {opacity: v1, duration: v2}; }; + + const configs = [{opacity: 0, duration: 500}, {opacity: 1, duration: 600}]; + renderToHtml(Template, {configs}); + expect(objectComps[0].config).toEqual({opacity: 0, duration: 500}); + expect(objectComps[1].config).toEqual({opacity: 1, duration: 600}); + + configs[0].duration = 1000; + renderToHtml(Template, {configs}); + expect(objectComps[0].config).toEqual({opacity: 0, duration: 1000}); + expect(objectComps[1].config).toEqual({opacity: 1, duration: 600}); + }); });