refactor(ivy): clean of #34804 from previous merge (#35022)

This is a follow up for https://github.com/angular/angular/pull/34804

PR Close #35022
This commit is contained in:
Miško Hevery 2020-01-28 16:26:56 -08:00 committed by Andrew Kushnir
parent ee8b8f52aa
commit c1cf46c5c3
19 changed files with 249 additions and 314 deletions

View File

@ -23,7 +23,7 @@ import {PlayerHandler} from './interfaces/player';
import {RElement, Renderer3, RendererFactory3, domRendererFactory3, isProceduralRenderer} from './interfaces/renderer'; import {RElement, Renderer3, RendererFactory3, domRendererFactory3, isProceduralRenderer} from './interfaces/renderer';
import {CONTEXT, HEADER_OFFSET, LView, LViewFlags, RootContext, RootContextFlags, TVIEW, TViewType} from './interfaces/view'; import {CONTEXT, HEADER_OFFSET, LView, LViewFlags, RootContext, RootContextFlags, TVIEW, TViewType} from './interfaces/view';
import {writeDirectClass, writeDirectStyle} from './node_manipulation'; import {writeDirectClass, writeDirectStyle} from './node_manipulation';
import {enterView, getPreviousOrParentTNode, leaveView, setActiveHostElement} from './state'; import {enterView, getPreviousOrParentTNode, leaveView, setSelectedIndex} from './state';
import {computeStaticStyling} from './styling/static_styling'; import {computeStaticStyling} from './styling/static_styling';
import {setUpAttributes} from './util/attrs_utils'; import {setUpAttributes} from './util/attrs_utils';
import {publishDefaultGlobalUtils} from './util/global_utils'; import {publishDefaultGlobalUtils} from './util/global_utils';
@ -239,7 +239,7 @@ export function createRootComponent<T>(
if (tView.firstCreatePass && if (tView.firstCreatePass &&
(componentDef.hostBindings !== null || componentDef.hostAttrs !== null)) { (componentDef.hostBindings !== null || componentDef.hostAttrs !== null)) {
const elementIndex = rootTNode.index - HEADER_OFFSET; const elementIndex = rootTNode.index - HEADER_OFFSET;
setActiveHostElement(elementIndex); setSelectedIndex(elementIndex);
const rootTView = rootLView[TVIEW]; const rootTView = rootLView[TVIEW];
addHostBindingsToExpandoInstructions(rootTView, componentDef); addHostBindingsToExpandoInstructions(rootTView, componentDef);

View File

@ -6,7 +6,7 @@
* found in the LICENSE file at https://angular.io/license * found in the LICENSE file at https://angular.io/license
*/ */
import {arrayMapSet} from '../../util/array_utils'; import {keyValueArraySet} from '../../util/array_utils';
import {getLView} from '../state'; import {getLView} from '../state';
import {interpolation1, interpolation2, interpolation3, interpolation4, interpolation5, interpolation6, interpolation7, interpolation8, interpolationV} from './interpolation'; import {interpolation1, interpolation2, interpolation3, interpolation4, interpolation5, interpolation6, interpolation7, interpolation8, interpolationV} from './interpolation';
import {checkStylingMap, classStringParser} from './styling'; import {checkStylingMap, classStringParser} from './styling';
@ -37,7 +37,7 @@ import {checkStylingMap, classStringParser} from './styling';
export function ɵɵclassMapInterpolate1(prefix: string, v0: any, suffix: string): void { export function ɵɵclassMapInterpolate1(prefix: string, v0: any, suffix: string): void {
const lView = getLView(); const lView = getLView();
const interpolatedValue = interpolation1(lView, prefix, v0, suffix); const interpolatedValue = interpolation1(lView, prefix, v0, suffix);
checkStylingMap(arrayMapSet, classStringParser, interpolatedValue, true); checkStylingMap(keyValueArraySet, classStringParser, interpolatedValue, true);
} }
/** /**
@ -67,7 +67,7 @@ export function ɵɵclassMapInterpolate2(
prefix: string, v0: any, i0: string, v1: any, suffix: string): void { prefix: string, v0: any, i0: string, v1: any, suffix: string): void {
const lView = getLView(); const lView = getLView();
const interpolatedValue = interpolation2(lView, prefix, v0, i0, v1, suffix); const interpolatedValue = interpolation2(lView, prefix, v0, i0, v1, suffix);
checkStylingMap(arrayMapSet, classStringParser, interpolatedValue, true); checkStylingMap(keyValueArraySet, classStringParser, interpolatedValue, true);
} }
/** /**
@ -100,7 +100,7 @@ export function ɵɵclassMapInterpolate3(
prefix: string, v0: any, i0: string, v1: any, i1: string, v2: any, suffix: string): void { prefix: string, v0: any, i0: string, v1: any, i1: string, v2: any, suffix: string): void {
const lView = getLView(); const lView = getLView();
const interpolatedValue = interpolation3(lView, prefix, v0, i0, v1, i1, v2, suffix); const interpolatedValue = interpolation3(lView, prefix, v0, i0, v1, i1, v2, suffix);
checkStylingMap(arrayMapSet, classStringParser, interpolatedValue, true); checkStylingMap(keyValueArraySet, classStringParser, interpolatedValue, true);
} }
/** /**
@ -136,7 +136,7 @@ export function ɵɵclassMapInterpolate4(
suffix: string): void { suffix: string): void {
const lView = getLView(); const lView = getLView();
const interpolatedValue = interpolation4(lView, prefix, v0, i0, v1, i1, v2, i2, v3, suffix); const interpolatedValue = interpolation4(lView, prefix, v0, i0, v1, i1, v2, i2, v3, suffix);
checkStylingMap(arrayMapSet, classStringParser, interpolatedValue, true); checkStylingMap(keyValueArraySet, classStringParser, interpolatedValue, true);
} }
/** /**
@ -175,7 +175,7 @@ export function ɵɵclassMapInterpolate5(
const lView = getLView(); const lView = getLView();
const interpolatedValue = const interpolatedValue =
interpolation5(lView, prefix, v0, i0, v1, i1, v2, i2, v3, i3, v4, suffix); interpolation5(lView, prefix, v0, i0, v1, i1, v2, i2, v3, i3, v4, suffix);
checkStylingMap(arrayMapSet, classStringParser, interpolatedValue, true); checkStylingMap(keyValueArraySet, classStringParser, interpolatedValue, true);
} }
/** /**
@ -216,7 +216,7 @@ export function ɵɵclassMapInterpolate6(
const lView = getLView(); const lView = getLView();
const interpolatedValue = const interpolatedValue =
interpolation6(lView, prefix, v0, i0, v1, i1, v2, i2, v3, i3, v4, i4, v5, suffix); interpolation6(lView, prefix, v0, i0, v1, i1, v2, i2, v3, i3, v4, i4, v5, suffix);
checkStylingMap(arrayMapSet, classStringParser, interpolatedValue, true); checkStylingMap(keyValueArraySet, classStringParser, interpolatedValue, true);
} }
/** /**
@ -259,7 +259,7 @@ export function ɵɵclassMapInterpolate7(
const lView = getLView(); const lView = getLView();
const interpolatedValue = const interpolatedValue =
interpolation7(lView, prefix, v0, i0, v1, i1, v2, i2, v3, i3, v4, i4, v5, i5, v6, suffix); interpolation7(lView, prefix, v0, i0, v1, i1, v2, i2, v3, i3, v4, i4, v5, i5, v6, suffix);
checkStylingMap(arrayMapSet, classStringParser, interpolatedValue, true); checkStylingMap(keyValueArraySet, classStringParser, interpolatedValue, true);
} }
/** /**
@ -305,7 +305,7 @@ export function ɵɵclassMapInterpolate8(
const lView = getLView(); const lView = getLView();
const interpolatedValue = interpolation8( const interpolatedValue = interpolation8(
lView, prefix, v0, i0, v1, i1, v2, i2, v3, i3, v4, i4, v5, i5, v6, i6, v7, suffix); lView, prefix, v0, i0, v1, i1, v2, i2, v3, i3, v4, i4, v5, i5, v6, i6, v7, suffix);
checkStylingMap(arrayMapSet, classStringParser, interpolatedValue, true); checkStylingMap(keyValueArraySet, classStringParser, interpolatedValue, true);
} }
/** /**
@ -334,5 +334,5 @@ export function ɵɵclassMapInterpolate8(
export function ɵɵclassMapInterpolateV(values: any[]): void { export function ɵɵclassMapInterpolateV(values: any[]): void {
const lView = getLView(); const lView = getLView();
const interpolatedValue = interpolationV(lView, values); const interpolatedValue = interpolationV(lView, values);
checkStylingMap(arrayMapSet, classStringParser, interpolatedValue, true); checkStylingMap(keyValueArraySet, classStringParser, interpolatedValue, true);
} }

View File

@ -8,7 +8,7 @@
import {AttributeMarker, ComponentTemplate} from '..'; import {AttributeMarker, ComponentTemplate} from '..';
import {SchemaMetadata} from '../../core'; import {SchemaMetadata} from '../../core';
import {ArrayMap} from '../../util/array_utils'; import {KeyValueArray} from '../../util/array_utils';
import {assertDefined} from '../../util/assert'; import {assertDefined} from '../../util/assert';
import {createNamedArrayType} from '../../util/named_array_type'; import {createNamedArrayType} from '../../util/named_array_type';
import {initNgDevMode} from '../../util/ng_dev_mode'; import {initNgDevMode} from '../../util/ng_dev_mode';
@ -176,9 +176,9 @@ class TNode implements ITNode {
public parent: TElementNode|TContainerNode|null, // public parent: TElementNode|TContainerNode|null, //
public projection: number|(ITNode|RNode[])[]|null, // public projection: number|(ITNode|RNode[])[]|null, //
public styles: string|null, // public styles: string|null, //
public residualStyles: ArrayMap<any>|undefined|null, // public residualStyles: KeyValueArray<any>|undefined|null, //
public classes: string|null, // public classes: string|null, //
public residualClasses: ArrayMap<any>|undefined|null, // public residualClasses: KeyValueArray<any>|undefined|null, //
public classBindings: TStylingRange, // public classBindings: TStylingRange, //
public styleBindings: TStylingRange, // public styleBindings: TStylingRange, //
public directives: TDirectiveDefs|null, // public directives: TDirectiveDefs|null, //
@ -241,7 +241,8 @@ class TNode implements ITNode {
export const TNodeDebug = TNode; export const TNodeDebug = TNode;
export type TNodeDebug = TNode; export type TNodeDebug = TNode;
export interface DebugStyleBindings extends Array<ArrayMap<any>|DebugStyleBinding|string|null> {} export interface DebugStyleBindings extends
Array<KeyValueArray<any>|DebugStyleBinding|string|null> {}
export interface DebugStyleBinding { export interface DebugStyleBinding {
key: TStylingKey; key: TStylingKey;
index: number; index: number;

View File

@ -31,12 +31,13 @@ import {isComponentDef, isComponentHost, isContentQueryHost, isLContainer, isRoo
import {CHILD_HEAD, CHILD_TAIL, CLEANUP, CONTEXT, DECLARATION_COMPONENT_VIEW, DECLARATION_VIEW, FLAGS, HEADER_OFFSET, HOST, INJECTOR, InitPhaseState, LView, LViewFlags, NEXT, PARENT, RENDERER, RENDERER_FACTORY, RootContext, RootContextFlags, SANITIZER, TData, TVIEW, TView, TViewType, T_HOST} from '../interfaces/view'; import {CHILD_HEAD, CHILD_TAIL, CLEANUP, CONTEXT, DECLARATION_COMPONENT_VIEW, DECLARATION_VIEW, FLAGS, HEADER_OFFSET, HOST, INJECTOR, InitPhaseState, LView, LViewFlags, NEXT, PARENT, RENDERER, RENDERER_FACTORY, RootContext, RootContextFlags, SANITIZER, TData, TVIEW, TView, TViewType, T_HOST} from '../interfaces/view';
import {assertNodeOfPossibleTypes} from '../node_assert'; import {assertNodeOfPossibleTypes} from '../node_assert';
import {isNodeMatchingSelectorList} from '../node_selector_matcher'; import {isNodeMatchingSelectorList} from '../node_selector_matcher';
import {clearActiveHostElement, enterView, getBindingsEnabled, getCheckNoChangesMode, getIsParent, getPreviousOrParentTNode, getSelectedIndex, leaveView, setActiveHostElement, setBindingIndex, setBindingRootForHostBindings, setCheckNoChangesMode, setCurrentQueryIndex, setPreviousOrParentTNode, setSelectedIndex} from '../state'; import {enterView, getBindingsEnabled, getCheckNoChangesMode, getIsParent, getPreviousOrParentTNode, getSelectedIndex, leaveView, setBindingIndex, setBindingRootForHostBindings, setCheckNoChangesMode, setCurrentQueryIndex, setPreviousOrParentTNode, setSelectedIndex} from '../state';
import {NO_CHANGE} from '../tokens'; import {NO_CHANGE} from '../tokens';
import {isAnimationProp, mergeHostAttrs} from '../util/attrs_utils'; import {isAnimationProp, mergeHostAttrs} from '../util/attrs_utils';
import {INTERPOLATION_DELIMITER, renderStringify, stringifyForError} from '../util/misc_utils'; import {INTERPOLATION_DELIMITER, renderStringify, stringifyForError} from '../util/misc_utils';
import {getLViewParent} from '../util/view_traversal_utils'; import {getLViewParent} from '../util/view_traversal_utils';
import {getComponentLViewByIndex, getNativeByIndex, getNativeByTNode, getTNode, isCreationMode, readPatchedLView, resetPreOrderHookFlags, viewAttachedToChangeDetector} from '../util/view_utils'; import {getComponentLViewByIndex, getNativeByIndex, getNativeByTNode, getTNode, isCreationMode, readPatchedLView, resetPreOrderHookFlags, viewAttachedToChangeDetector} from '../util/view_utils';
import {selectIndexInternal} from './advance'; import {selectIndexInternal} from './advance';
import {LCleanup, LViewBlueprint, MatchesArray, TCleanup, TNodeDebug, TNodeInitialInputs, TNodeLocalNames, TViewComponents, TViewConstructor, attachLContainerDebug, attachLViewDebug, cloneToLViewFromTViewBlueprint, cloneToTViewData} from './lview_debug'; import {LCleanup, LViewBlueprint, MatchesArray, TCleanup, TNodeDebug, TNodeInitialInputs, TNodeLocalNames, TViewComponents, TViewConstructor, attachLContainerDebug, attachLViewDebug, cloneToLViewFromTViewBlueprint, cloneToTViewData} from './lview_debug';
@ -49,7 +50,7 @@ import {LCleanup, LViewBlueprint, MatchesArray, TCleanup, TNodeDebug, TNodeIniti
const _CLEAN_PROMISE = (() => Promise.resolve(null))(); const _CLEAN_PROMISE = (() => Promise.resolve(null))();
/** /**
* Process the `TVIew.expandoInstructions`. (Execute the `hostBindings`.) * Process the `TView.expandoInstructions`. (Execute the `hostBindings`.)
* *
* @param tView `TView` containing the `expandoInstructions` * @param tView `TView` containing the `expandoInstructions`
* @param lView `LView` associated with the `TView` * @param lView `LView` associated with the `TView`
@ -62,7 +63,7 @@ export function setHostBindingsByExecutingExpandoInstructions(tView: TView, lVie
let bindingRootIndex = tView.expandoStartIndex; let bindingRootIndex = tView.expandoStartIndex;
let currentDirectiveIndex = -1; let currentDirectiveIndex = -1;
let currentElementIndex = -1; let currentElementIndex = -1;
// TODO(misko): PERF It is possible to get here with `TVIew.expandoInstructions` containing no // TODO(misko): PERF It is possible to get here with `TView.expandoInstructions` containing no
// functions to execute. This is wasteful as there is no work to be done, but we still need // functions to execute. This is wasteful as there is no work to be done, but we still need
// to iterate over the instructions. // to iterate over the instructions.
// In example of this is in this test: `host_binding_spec.ts` // In example of this is in this test: `host_binding_spec.ts`
@ -81,7 +82,7 @@ export function setHostBindingsByExecutingExpandoInstructions(tView: TView, lVie
// TODO(misko): PERF This should be refactored to use `~instruction` as that does not // TODO(misko): PERF This should be refactored to use `~instruction` as that does not
// suffer from `-0` and it is faster/more compact. // suffer from `-0` and it is faster/more compact.
currentElementIndex = 0 - instruction; currentElementIndex = 0 - instruction;
setActiveHostElement(currentElementIndex); setSelectedIndex(currentElementIndex);
// Injector block and providers are taken into account. // Injector block and providers are taken into account.
const providerCount = (expandoInstructions[++i] as number); const providerCount = (expandoInstructions[++i] as number);
@ -112,7 +113,7 @@ export function setHostBindingsByExecutingExpandoInstructions(tView: TView, lVie
} }
} }
} finally { } finally {
clearActiveHostElement(); setSelectedIndex(-1);
} }
} }
@ -528,7 +529,7 @@ function executeTemplate<T>(
lView: LView, templateFn: ComponentTemplate<T>, rf: RenderFlags, context: T) { lView: LView, templateFn: ComponentTemplate<T>, rf: RenderFlags, context: T) {
const prevSelectedIndex = getSelectedIndex(); const prevSelectedIndex = getSelectedIndex();
try { try {
clearActiveHostElement(); setSelectedIndex(-1);
if (rf & RenderFlags.Update && lView.length > HEADER_OFFSET) { if (rf & RenderFlags.Update && lView.length > HEADER_OFFSET) {
// When we're updating, inherently select 0 so we don't // When we're updating, inherently select 0 so we don't
// have to generate that instruction for most update blocks. // have to generate that instruction for most update blocks.
@ -1264,7 +1265,7 @@ function invokeDirectivesHostBindings(tView: TView, lView: LView, tNode: TNode)
const firstCreatePass = tView.firstCreatePass; const firstCreatePass = tView.firstCreatePass;
const elementIndex = tNode.index - HEADER_OFFSET; const elementIndex = tNode.index - HEADER_OFFSET;
try { try {
setActiveHostElement(elementIndex); setSelectedIndex(elementIndex);
for (let i = start; i < end; i++) { for (let i = start; i < end; i++) {
const def = tView.data[i] as DirectiveDef<any>; const def = tView.data[i] as DirectiveDef<any>;
const directive = lView[i]; const directive = lView[i];
@ -1275,7 +1276,7 @@ function invokeDirectivesHostBindings(tView: TView, lView: LView, tNode: TNode)
} }
} }
} finally { } finally {
clearActiveHostElement(); setSelectedIndex(-1);
} }
} }

View File

@ -9,7 +9,7 @@
import {SafeValue, unwrapSafeValue} from '../../sanitization/bypass'; import {SafeValue, unwrapSafeValue} from '../../sanitization/bypass';
import {stylePropNeedsSanitization, ɵɵsanitizeStyle} from '../../sanitization/sanitization'; import {stylePropNeedsSanitization, ɵɵsanitizeStyle} from '../../sanitization/sanitization';
import {StyleSanitizeFn} from '../../sanitization/style_sanitizer'; import {StyleSanitizeFn} from '../../sanitization/style_sanitizer';
import {ArrayMap, arrayMapGet, arrayMapSet} from '../../util/array_utils'; import {KeyValueArray, keyValueArrayGet, keyValueArraySet} from '../../util/array_utils';
import {assertDefined, assertEqual, assertGreaterThanOrEqual, assertLessThan, assertNotEqual, assertNotSame, throwError} from '../../util/assert'; import {assertDefined, assertEqual, assertGreaterThanOrEqual, assertLessThan, assertNotEqual, assertNotSame, throwError} from '../../util/assert';
import {EMPTY_ARRAY} from '../../util/empty'; import {EMPTY_ARRAY} from '../../util/empty';
import {concatStringsWithSpace, stringify} from '../../util/stringify'; import {concatStringsWithSpace, stringify} from '../../util/stringify';
@ -122,22 +122,22 @@ export function ɵɵclassProp(
export function ɵɵstyleMap( export function ɵɵstyleMap(
styles: {[styleName: string]: any} | Map<string, string|number|null|undefined>| string | styles: {[styleName: string]: any} | Map<string, string|number|null|undefined>| string |
undefined | null): void { undefined | null): void {
checkStylingMap(styleArrayMapSet, styleStringParser, styles, false); checkStylingMap(styleKeyValueArraySet, styleStringParser, styles, false);
} }
/** /**
* Parse text as style and add values to ArrayMap. * Parse text as style and add values to KeyValueArray.
* *
* This code is pulled out to a separate function so that it can be tree shaken away if it is not * This code is pulled out to a separate function so that it can be tree shaken away if it is not
* needed. It is only reference from `ɵɵstyleMap`. * needed. It is only referenced from `ɵɵstyleMap`.
* *
* @param arrayMap ArrayMap to add parsed values to. * @param keyValueArray KeyValueArray to add parsed values to.
* @param text text to parse. * @param text text to parse.
*/ */
export function styleStringParser(arrayMap: ArrayMap<any>, text: string): void { export function styleStringParser(keyValueArray: KeyValueArray<any>, text: string): void {
for (let i = parseStyle(text); i >= 0; i = parseStyleNext(text, i)) { for (let i = parseStyle(text); i >= 0; i = parseStyleNext(text, i)) {
styleArrayMapSet(arrayMap, getLastParsedKey(text), getLastParsedValue(text)); styleKeyValueArraySet(keyValueArray, getLastParsedKey(text), getLastParsedValue(text));
} }
} }
@ -163,21 +163,21 @@ export function styleStringParser(arrayMap: ArrayMap<any>, text: string): void {
export function ɵɵclassMap( export function ɵɵclassMap(
classes: {[className: string]: boolean | undefined | null} | classes: {[className: string]: boolean | undefined | null} |
Map<string, boolean|undefined|null>| Set<string>| string[] | string | undefined | null): void { Map<string, boolean|undefined|null>| Set<string>| string[] | string | undefined | null): void {
checkStylingMap(arrayMapSet, classStringParser, classes, true); checkStylingMap(keyValueArraySet, classStringParser, classes, true);
} }
/** /**
* Parse text as class and add values to ArrayMap. * Parse text as class and add values to KeyValueArray.
* *
* This code is pulled out to a separate function so that it can be tree shaken away if it is not * This code is pulled out to a separate function so that it can be tree shaken away if it is not
* needed. It is only reference from `ɵɵclassMap`. * needed. It is only referenced from `ɵɵclassMap`.
* *
* @param arrayMap ArrayMap to add parsed values to. * @param keyValueArray KeyValueArray to add parsed values to.
* @param text text to parse. * @param text text to parse.
*/ */
export function classStringParser(arrayMap: ArrayMap<any>, text: string): void { export function classStringParser(keyValueArray: KeyValueArray<any>, text: string): void {
for (let i = parseClassName(text); i >= 0; i = parseClassNameNext(text, i)) { for (let i = parseClassName(text); i >= 0; i = parseClassNameNext(text, i)) {
arrayMapSet(arrayMap, getLastParsedKey(text), true); keyValueArraySet(keyValueArray, getLastParsedKey(text), true);
} }
} }
@ -199,7 +199,7 @@ export function checkStylingProperty(
// 2. one for the intermittent-value / TStylingRange // 2. one for the intermittent-value / TStylingRange
const bindingIndex = incrementBindingIndex(2); const bindingIndex = incrementBindingIndex(2);
if (tView.firstUpdatePass) { if (tView.firstUpdatePass) {
stylingPropertyFirstUpdatePass(tView, prop, bindingIndex, isClassBased); stylingFirstUpdatePass(tView, prop, bindingIndex, isClassBased);
} }
if (value !== NO_CHANGE && bindingUpdated(lView, bindingIndex, value)) { if (value !== NO_CHANGE && bindingUpdated(lView, bindingIndex, value)) {
// This is a work around. Once PR#34480 lands the sanitizer is passed explicitly and this line // This is a work around. Once PR#34480 lands the sanitizer is passed explicitly and this line
@ -221,19 +221,24 @@ export function checkStylingProperty(
/** /**
* Common code between `ɵɵclassMap` and `ɵɵstyleMap`. * Common code between `ɵɵclassMap` and `ɵɵstyleMap`.
* *
* @param tStylingMapKey See `STYLE_MAP_STYLING_KEY` and `CLASS_MAP_STYLING_KEY`. * @param keyValueArraySet (See `keyValueArraySet` in "util/array_utils") Gets passed in as a
* @param value binding value. * function so that
* `style` can pass in version which does sanitization. This is done for tree shaking
* purposes.
* @param stringParser Parser used to parse `value` if `string`. (Passed in as `style` and `class`
* have different parsers.)
* @param value bound value from application
* @param isClassBased `true` if `class` change (`false` if `style`) * @param isClassBased `true` if `class` change (`false` if `style`)
*/ */
export function checkStylingMap( export function checkStylingMap(
arrayMapSet: (arrayMap: ArrayMap<any>, key: string, value: any) => void, keyValueArraySet: (keyValueArray: KeyValueArray<any>, key: string, value: any) => void,
stringParser: (styleArrayMap: ArrayMap<any>, text: string) => void, value: any|NO_CHANGE, stringParser: (styleKeyValueArray: KeyValueArray<any>, text: string) => void,
isClassBased: boolean): void { value: any|NO_CHANGE, isClassBased: boolean): void {
const lView = getLView(); const lView = getLView();
const tView = lView[TVIEW]; const tView = lView[TVIEW];
const bindingIndex = incrementBindingIndex(2); const bindingIndex = incrementBindingIndex(2);
if (tView.firstUpdatePass) { if (tView.firstUpdatePass) {
stylingPropertyFirstUpdatePass(tView, null, bindingIndex, isClassBased); stylingFirstUpdatePass(tView, null, bindingIndex, isClassBased);
} }
if (value !== NO_CHANGE && bindingUpdated(lView, bindingIndex, value)) { if (value !== NO_CHANGE && bindingUpdated(lView, bindingIndex, value)) {
// `getSelectedIndex()` should be here (rather than in instruction) so that it is guarded by the // `getSelectedIndex()` should be here (rather than in instruction) so that it is guarded by the
@ -252,8 +257,8 @@ export function checkStylingMap(
// Instead VE just ignores the static completely if dynamic binding is present. // Instead VE just ignores the static completely if dynamic binding is present.
// Because of locality we have already set the static portion because we don't know if there // Because of locality we have already set the static portion because we don't know if there
// is a dynamic portion until later. If we would ignore the static portion it would look like // is a dynamic portion until later. If we would ignore the static portion it would look like
// tha the binding has removed it. This would confuse `[ngStyle]`/`[ngClass]` to do the wrong // the binding has removed it. This would confuse `[ngStyle]`/`[ngClass]` to do the wrong
// thing as it would think tha the static portion was removed. For this reason we // thing as it would think that the static portion was removed. For this reason we
// concatenate it so that `[ngStyle]`/`[ngClass]` can continue to work on changed. // concatenate it so that `[ngStyle]`/`[ngClass]` can continue to work on changed.
let staticPrefix = isClassBased ? tNode.classes : tNode.styles; let staticPrefix = isClassBased ? tNode.classes : tNode.styles;
ngDevMode && isClassBased === false && staticPrefix !== null && ngDevMode && isClassBased === false && staticPrefix !== null &&
@ -268,7 +273,7 @@ export function checkStylingMap(
} else { } else {
updateStylingMap( updateStylingMap(
tView, tNode, lView, lView[RENDERER], lView[bindingIndex + 1], tView, tNode, lView, lView[RENDERER], lView[bindingIndex + 1],
lView[bindingIndex + 1] = toStylingArrayMap(arrayMapSet, stringParser, value), lView[bindingIndex + 1] = toStylingKeyValueArray(keyValueArraySet, stringParser, value),
isClassBased, bindingIndex); isClassBased, bindingIndex);
} }
} }
@ -290,12 +295,11 @@ function isInHostBindings(tView: TView, bindingIndex: number): boolean {
* using `insertTStylingBinding`. * using `insertTStylingBinding`.
* *
* @param tView `TView` where the binding linked list will be stored. * @param tView `TView` where the binding linked list will be stored.
* @param prop Property/key of the binding. * @param tStylingKey Property/key of the binding.
* @param suffix Optional suffix or Sanitization function.
* @param bindingIndex Index of binding associated with the `prop` * @param bindingIndex Index of binding associated with the `prop`
* @param isClassBased `true` if `class` change (`false` if `style`) * @param isClassBased `true` if `class` change (`false` if `style`)
*/ */
function stylingPropertyFirstUpdatePass( function stylingFirstUpdatePass(
tView: TView, tStylingKey: TStylingKey, bindingIndex: number, isClassBased: boolean): void { tView: TView, tStylingKey: TStylingKey, bindingIndex: number, isClassBased: boolean): void {
ngDevMode && assertFirstUpdatePass(tView); ngDevMode && assertFirstUpdatePass(tView);
const tData = tView.data; const tData = tView.data;
@ -429,16 +433,17 @@ function setTemplateHeadTStylingKey(
tData[getTStylingRangePrev(bindings)] = tStylingKey; tData[getTStylingRangePrev(bindings)] = tStylingKey;
} }
function collectResidual(tNode: TNode, isClassBased: boolean): ArrayMap<any>|null { function collectResidual(tNode: TNode, isClassBased: boolean): KeyValueArray<any>|null {
let residual: ArrayMap<any>|null|undefined = undefined; let residual: KeyValueArray<any>|null|undefined = undefined;
const directives = tNode.directives; const directives = tNode.directives;
if (directives) { if (directives) {
for (let i = directives[DirectiveDefs.STYLING_CURSOR] + 1; i < directives.length; i++) { for (let i = directives[DirectiveDefs.STYLING_CURSOR] + 1; i < directives.length; i++) {
const attrs = (directives[i] as DirectiveDef<any>).hostAttrs; const attrs = (directives[i] as DirectiveDef<any>).hostAttrs;
residual = collectStylingFromTAttrs(residual, attrs, isClassBased) as ArrayMap<any>| null; residual =
collectStylingFromTAttrs(residual, attrs, isClassBased) as KeyValueArray<any>| null;
} }
} }
return collectStylingFromTAttrs(residual, tNode.attrs, isClassBased) as ArrayMap<any>| null; return collectStylingFromTAttrs(residual, tNode.attrs, isClassBased) as KeyValueArray<any>| null;
} }
/** /**
@ -505,7 +510,8 @@ function collectStylingFromTAttrs(
if (!Array.isArray(stylingKey)) { if (!Array.isArray(stylingKey)) {
stylingKey = stylingKey === undefined ? [] : ['', stylingKey] as any; stylingKey = stylingKey === undefined ? [] : ['', stylingKey] as any;
} }
arrayMapSet(stylingKey as ArrayMap<any>, item, isClassBased ? true : attrs[++i]); keyValueArraySet(
stylingKey as KeyValueArray<any>, item, isClassBased ? true : attrs[++i]);
} }
} }
} }
@ -525,71 +531,78 @@ export function getHostDirectiveDef(tData: TData): DirectiveDef<any>|null {
} }
/** /**
* Convert user input to `ArrayMap`. * Convert user input to `KeyValueArray`.
* *
* This function takes user input which could be `string`, Object literal, or iterable and converts * This function takes user input which could be `string`, Object literal, or iterable and converts
* it into a consistent representation. The output of this is `ArrayMap` (which is an array where * it into a consistent representation. The output of this is `KeyValueArray` (which is an array
* where
* even indexes contain keys and odd indexes contain values for those keys). * even indexes contain keys and odd indexes contain values for those keys).
* *
* The advantage of converting to `ArrayMap` is that we can perform diff in a input independent way. * The advantage of converting to `KeyValueArray` is that we can perform diff in an input
* independent
* way.
* (ie we can compare `foo bar` to `['bar', 'baz'] and determine a set of changes which need to be * (ie we can compare `foo bar` to `['bar', 'baz'] and determine a set of changes which need to be
* applied) * applied)
* *
* The fact that `ArrayMap` is sorted is very important because it allows us to compute the * The fact that `KeyValueArray` is sorted is very important because it allows us to compute the
* difference in linear fashion without the need to allocate any additional data. * difference in linear fashion without the need to allocate any additional data.
* *
* For example if we kept this as a `Map` we would have to iterate over previous `Map` to determine * For example if we kept this as a `Map` we would have to iterate over previous `Map` to determine
* which values need to be delete, over the new `Map` to determine additions, and we would have to * which values need to be deleted, over the new `Map` to determine additions, and we would have to
* keep additional `Map` to keep track of duplicates or items which have not yet been visited. * keep additional `Map` to keep track of duplicates or items which have not yet been visited.
* *
* @param keyValueArraySet (See `keyValueArraySet` in "util/array_utils") Gets passed in as a
* function so that
* `style` can pass in version which does sanitization. This is done for tree shaking
* purposes.
* @param stringParser The parser is passed in so that it will be tree shakable. See * @param stringParser The parser is passed in so that it will be tree shakable. See
* `styleStringParser` and `classStringParser` * `styleStringParser` and `classStringParser`
* @param value The value to parse/convert to `ArrayMap` * @param value The value to parse/convert to `KeyValueArray`
*/ */
export function toStylingArrayMap( export function toStylingKeyValueArray(
arrayMapSet: (arrayMap: ArrayMap<any>, key: string, value: any) => void, keyValueArraySet: (keyValueArray: KeyValueArray<any>, key: string, value: any) => void,
stringParser: (styleArrayMap: ArrayMap<any>, text: string) => void, value: string|string[]| stringParser: (styleKeyValueArray: KeyValueArray<any>, text: string) => void, value: string|
{[key: string]: any}|Map<any, any>|Set<any>|null|undefined): ArrayMap<any> { string[]|{[key: string]: any}|Map<any, any>|Set<any>|null|undefined): KeyValueArray<any> {
if (value === null || value === undefined || value === '') return EMPTY_ARRAY as any; if (value == null /*|| value === undefined */ || value === '') return EMPTY_ARRAY as any;
const styleArrayMap: ArrayMap<any> = [] as any; const styleKeyValueArray: KeyValueArray<any> = [] as any;
if (Array.isArray(value)) { if (Array.isArray(value)) {
for (let i = 0; i < value.length; i++) { for (let i = 0; i < value.length; i++) {
arrayMapSet(styleArrayMap, value[i], true); keyValueArraySet(styleKeyValueArray, value[i], true);
} }
} else if (typeof value === 'object') { } else if (typeof value === 'object') {
if (value instanceof Map) { if (value instanceof Map) {
value.forEach((v, k) => arrayMapSet(styleArrayMap, k, v)); value.forEach((v, k) => keyValueArraySet(styleKeyValueArray, k, v));
} else if (value instanceof Set) { } else if (value instanceof Set) {
value.forEach((k) => arrayMapSet(styleArrayMap, k, true)); value.forEach((k) => keyValueArraySet(styleKeyValueArray, k, true));
} else { } else {
for (const key in value) { for (const key in value) {
if (value.hasOwnProperty(key)) { if (value.hasOwnProperty(key)) {
arrayMapSet(styleArrayMap, key, value[key]); keyValueArraySet(styleKeyValueArray, key, value[key]);
} }
} }
} }
} else if (typeof value === 'string') { } else if (typeof value === 'string') {
stringParser(styleArrayMap, value); stringParser(styleKeyValueArray, value);
} else { } else {
ngDevMode && throwError('Unsupported styling type ' + typeof value); ngDevMode && throwError('Unsupported styling type ' + typeof value + ': ' + value);
} }
return styleArrayMap; return styleKeyValueArray;
} }
/** /**
* Set a `value` for a `key` taking style sanitization into account. * Set a `value` for a `key` taking style sanitization into account.
* *
* See: `arrayMapSet` for details * See: `keyValueArraySet` for details
* *
* @param arrayMap ArrayMap to add to. * @param keyValueArray KeyValueArray to add to.
* @param key Style key to add. (This key will be checked if it needs sanitization) * @param key Style key to add. (This key will be checked if it needs sanitization)
* @param value The value to set (If key needs sanitization it will be sanitized) * @param value The value to set (If key needs sanitization it will be sanitized)
*/ */
function styleArrayMapSet(arrayMap: ArrayMap<any>, key: string, value: any) { function styleKeyValueArraySet(keyValueArray: KeyValueArray<any>, key: string, value: any) {
if (stylePropNeedsSanitization(key)) { if (stylePropNeedsSanitization(key)) {
value = ɵɵsanitizeStyle(value); value = ɵɵsanitizeStyle(value);
} }
arrayMapSet(arrayMap, key, value); keyValueArraySet(keyValueArray, key, value);
} }
/** /**
@ -598,34 +611,38 @@ function styleArrayMapSet(arrayMap: ArrayMap<any>, key: string, value: any) {
* Map based styling could be anything which contains more than one binding. For example `string`, * Map based styling could be anything which contains more than one binding. For example `string`,
* `Map`, `Set` or object literal. Dealing with all of these types would complicate the logic so * `Map`, `Set` or object literal. Dealing with all of these types would complicate the logic so
* instead this function expects that the complex input is first converted into normalized * instead this function expects that the complex input is first converted into normalized
* `ArrayMap`. The advantage of normalization is that we get the values sorted, which makes it very * `KeyValueArray`. The advantage of normalization is that we get the values sorted, which makes it
* very
* cheap to compute deltas between the previous and current value. * cheap to compute deltas between the previous and current value.
* *
* @param tView Associated `TView.data` contains the linked list of binding priorities. * @param tView Associated `TView.data` contains the linked list of binding priorities.
* @param tNode `TNode` where the binding is located. * @param tNode `TNode` where the binding is located.
* @param lView `LView` contains the values associated with other styling binding at this `TNode`. * @param lView `LView` contains the values associated with other styling binding at this `TNode`.
* @param renderer Renderer to use if any updates. * @param renderer Renderer to use if any updates.
* @param oldArrayMap Previous value represented as `ArrayMap` * @param oldKeyValueArray Previous value represented as `KeyValueArray`
* @param newArrayMap Current value represented as `ArrayMap` * @param newKeyValueArray Current value represented as `KeyValueArray`
* @param isClassBased `true` if `class` (`false` if `style`) * @param isClassBased `true` if `class` (`false` if `style`)
* @param bindingIndex Binding index of the binding. * @param bindingIndex Binding index of the binding.
*/ */
function updateStylingMap( function updateStylingMap(
tView: TView, tNode: TNode, lView: LView, renderer: Renderer3, oldArrayMap: ArrayMap<any>, tView: TView, tNode: TNode, lView: LView, renderer: Renderer3,
newArrayMap: ArrayMap<any>, isClassBased: boolean, bindingIndex: number) { oldKeyValueArray: KeyValueArray<any>, newKeyValueArray: KeyValueArray<any>,
if (oldArrayMap as ArrayMap<any>| NO_CHANGE === NO_CHANGE) { isClassBased: boolean, bindingIndex: number) {
// ON first execution the oldArrayMap is NO_CHANGE => treat is as empty ArrayMap. if (oldKeyValueArray as KeyValueArray<any>| NO_CHANGE === NO_CHANGE) {
oldArrayMap = EMPTY_ARRAY as any; // On first execution the oldKeyValueArray is NO_CHANGE => treat it as empty KeyValueArray.
oldKeyValueArray = EMPTY_ARRAY as any;
} }
let oldIndex = 0; let oldIndex = 0;
let newIndex = 0; let newIndex = 0;
let oldKey: string|null = 0 < oldArrayMap.length ? oldArrayMap[0] : null; let oldKey: string|null = 0 < oldKeyValueArray.length ? oldKeyValueArray[0] : null;
let newKey: string|null = 0 < newArrayMap.length ? newArrayMap[0] : null; let newKey: string|null = 0 < newKeyValueArray.length ? newKeyValueArray[0] : null;
while (oldKey !== null || newKey !== null) { while (oldKey !== null || newKey !== null) {
ngDevMode && assertLessThan(oldIndex, 999, 'Are we stuck in infinite loop?'); ngDevMode && assertLessThan(oldIndex, 999, 'Are we stuck in infinite loop?');
ngDevMode && assertLessThan(newIndex, 999, 'Are we stuck in infinite loop?'); ngDevMode && assertLessThan(newIndex, 999, 'Are we stuck in infinite loop?');
const oldValue = oldIndex < oldArrayMap.length ? oldArrayMap[oldIndex + 1] : undefined; const oldValue =
const newValue = newIndex < newArrayMap.length ? newArrayMap[newIndex + 1] : undefined; oldIndex < oldKeyValueArray.length ? oldKeyValueArray[oldIndex + 1] : undefined;
const newValue =
newIndex < newKeyValueArray.length ? newKeyValueArray[newIndex + 1] : undefined;
let setKey: string|null = null; let setKey: string|null = null;
let setValue: any = undefined; let setValue: any = undefined;
if (oldKey === newKey) { if (oldKey === newKey) {
@ -637,11 +654,16 @@ function updateStylingMap(
setValue = newValue; setValue = newValue;
} }
} else if (newKey === null || oldKey !== null && oldKey < newKey !) { } else if (newKey === null || oldKey !== null && oldKey < newKey !) {
// DELETE: oldKey key is missing or we did not find the oldKey in the newValue. // DELETE: oldKey key is missing or we did not find the oldKey in the newValue
// (because the keyValueArray is sorted and `newKey` is found later alphabetically).
// `"background" < "color"` so we need to delete `"background"` because it is not found in the
// new array.
oldIndex += 2; oldIndex += 2;
setKey = oldKey; setKey = oldKey;
} else { } else {
// CREATE: newKey is less than oldKey (or no oldKey) => we have new key. // CREATE: newKey's is earlier alphabetically than oldKey's (or no oldKey) => we have new key.
// `"color" > "background"` so we need to add `color` because it is in new array but not in
// old array.
ngDevMode && assertDefined(newKey, 'Expecting to have a valid key'); ngDevMode && assertDefined(newKey, 'Expecting to have a valid key');
newIndex += 2; newIndex += 2;
setKey = newKey; setKey = newKey;
@ -650,8 +672,8 @@ function updateStylingMap(
if (setKey !== null) { if (setKey !== null) {
updateStyling(tView, tNode, lView, renderer, setKey, setValue, isClassBased, bindingIndex); updateStyling(tView, tNode, lView, renderer, setKey, setValue, isClassBased, bindingIndex);
} }
oldKey = oldIndex < oldArrayMap.length ? oldArrayMap[oldIndex] : null; oldKey = oldIndex < oldKeyValueArray.length ? oldKeyValueArray[oldIndex] : null;
newKey = newIndex < newArrayMap.length ? newArrayMap[newIndex] : null; newKey = newIndex < newKeyValueArray.length ? newKeyValueArray[newIndex] : null;
} }
} }
@ -668,7 +690,7 @@ function updateStylingMap(
* @param lView `LView` contains the values associated with other styling binding at this `TNode`. * @param lView `LView` contains the values associated with other styling binding at this `TNode`.
* @param renderer Renderer to use if any updates. * @param renderer Renderer to use if any updates.
* @param prop Either style property name or a class name. * @param prop Either style property name or a class name.
* @param value Either style vale for `prop` or `true`/`false` if `prop` is class. * @param value Either style value for `prop` or `true`/`false` if `prop` is class.
* @param isClassBased `true` if `class` (`false` if `style`) * @param isClassBased `true` if `class` (`false` if `style`)
* @param bindingIndex Binding index of the binding. * @param bindingIndex Binding index of the binding.
*/ */
@ -700,20 +722,22 @@ function updateStyling(
} }
/** /**
* Search for styling value with higher priority which is overwriting current value. * Search for styling value with higher priority which is overwriting current value, or a
* value of lower priority to which we should fall back if the value is `undefined`.
* *
* When value is being applied at a location related values need to be consulted. * When value is being applied at a location, related values need to be consulted.
* - If there is a higher priority binding, we should be using that one instead. * - If there is a higher priority binding, we should be using that one instead.
* For example `<div [style]="{color:exp1}" [style.color]="exp2">` change to `exp1` * For example `<div [style]="{color:exp1}" [style.color]="exp2">` change to `exp1`
* requires that we check `exp2` to see if it is set to value other than `undefined`. * requires that we check `exp2` to see if it is set to value other than `undefined`.
* - If there is a lower priority binding and we are changing to `undefined` * - If there is a lower priority binding and we are changing to `undefined`
* For example `<div [style]="{color:exp1}" [style.color]="exp2">` change to `exp2` to * For example `<div [style]="{color:exp1}" [style.color]="exp2">` change to `exp2` to
* `undefined` requires that we check `exp` (and static values) and use that as new value. * `undefined` requires that we check `exp1` (and static values) and use that as new value.
* *
* NOTE: The styling stores two values. * NOTE: The styling stores two values.
* 1. The raw value which came from the application is stored at `index + 0` location. (This value * 1. The raw value which came from the application is stored at `index + 0` location. (This value
* is used for dirty checking). * is used for dirty checking).
* 2. The normalized value (converted to `ArrayMap` if map and sanitized) is stored at `index + 1`. * 2. The normalized value (converted to `KeyValueArray` if map and sanitized) is stored at `index +
* 1`.
* The advantage of storing the sanitized value is that once the value is written we don't need * The advantage of storing the sanitized value is that once the value is written we don't need
* to worry about sanitizing it later or keeping track of the sanitizer. * to worry about sanitizing it later or keeping track of the sanitizer.
* *
@ -731,32 +755,38 @@ function updateStyling(
function findStylingValue( function findStylingValue(
tData: TData, tNode: TNode | null, lView: LView, prop: string, index: number, tData: TData, tNode: TNode | null, lView: LView, prop: string, index: number,
isClassBased: boolean): any { isClassBased: boolean): any {
// `TNode` to use for resolving static styling. Also controls search direction.
// - `TNode` search next and quit as soon as `isStylingValuePresent(value)` is true.
// If no value found consult `tNode.residualStyle`/`tNode.residualClass` for default value.
// - `null` search prev and go all the way to end. Return last value where
// `isStylingValuePresent(value)` is true.
const isPrevDirection = tNode === null;
let value: any = undefined; let value: any = undefined;
while (index > 0) { while (index > 0) {
const rawKey = tData[index] as TStylingKey; const rawKey = tData[index] as TStylingKey;
const containsStatics = Array.isArray(rawKey); const containsStatics = Array.isArray(rawKey);
// Unwrap the key if we contain static values. // Unwrap the key if we contain static values.
const key = containsStatics ? (rawKey as string[])[1] : rawKey; const key = containsStatics ? (rawKey as string[])[1] : rawKey;
let currentValue = key === null ? arrayMapGet(lView[index + 1], prop) : let currentValue = key === null ? keyValueArrayGet(lView[index + 1], prop) :
key === prop ? lView[index + 1] : undefined; key === prop ? lView[index + 1] : undefined;
if (containsStatics && !isStylingValuePresent(currentValue)) { if (containsStatics && !isStylingValuePresent(currentValue)) {
currentValue = arrayMapGet(rawKey as ArrayMap<any>, prop); currentValue = keyValueArrayGet(rawKey as KeyValueArray<any>, prop);
} }
if (isStylingValuePresent(currentValue)) { if (isStylingValuePresent(currentValue)) {
value = currentValue; value = currentValue;
if (tNode === null) { if (isPrevDirection) {
return value; return value;
} }
} }
const tRange = tData[index + 1] as TStylingRange; const tRange = tData[index + 1] as TStylingRange;
index = tNode === null ? getTStylingRangePrev(tRange) : getTStylingRangeNext(tRange); index = isPrevDirection ? getTStylingRangePrev(tRange) : getTStylingRangeNext(tRange);
} }
if (tNode !== null) { if (tNode !== null) {
// in case where we are going in next direction AND we did not find anything, we need to // in case where we are going in next direction AND we did not find anything, we need to
// consult residual styling // consult residual styling
let residual = isClassBased ? tNode.residualClasses : tNode.residualStyles; let residual = isClassBased ? tNode.residualClasses : tNode.residualStyles;
if (residual != null /** OR residual !=== undefined */) { if (residual != null /** OR residual !=== undefined */) {
value = arrayMapGet(residual !, prop); value = keyValueArrayGet(residual !, prop);
} }
} }
return value; return value;
@ -776,38 +806,6 @@ function isStylingValuePresent(value: any): boolean {
return value !== undefined; return value !== undefined;
} }
/**
* Lazily computes `tNode.classesMap`/`tNode.stylesMap`.
*
* This code is here because we don't want to included it in `elementStart` as it would make hello
* world bigger even if no styling would be present. Instead we initialize the values here so that
* tree shaking will only bring it in if styling is present.
*
* @param tNode `TNode` to initialize.
*/
export function initializeStylingStaticArrayMap(tNode: TNode) {
ngDevMode && assertEqual(tNode.residualClasses, undefined, 'Already initialized!');
ngDevMode && assertEqual(tNode.residualStyles, undefined, 'Already initialized!');
let styleMap: ArrayMap<any>|null = null;
let classMap: ArrayMap<any>|null = null;
const mergeAttrs = tNode.mergedAttrs || EMPTY_ARRAY as TAttributes;
let mode: AttributeMarker = AttributeMarker.ImplicitAttributes;
for (let i = 0; i < mergeAttrs.length; i++) {
let item = mergeAttrs[i];
if (typeof item === 'number') {
mode = item;
} else if (mode === AttributeMarker.Classes) {
classMap = classMap || [] as any;
arrayMapSet(classMap !, item as string, true);
} else if (mode === AttributeMarker.Styles) {
styleMap = styleMap || [] as any;
arrayMapSet(styleMap !, item as string, mergeAttrs[++i] as string);
}
}
tNode.residualClasses = classMap;
tNode.residualStyles = styleMap;
}
/** /**
* Sanitizes or adds suffix to the value. * Sanitizes or adds suffix to the value.
* *
@ -818,7 +816,7 @@ export function initializeStylingStaticArrayMap(tNode: TNode) {
function normalizeAndApplySuffixOrSanitizer( function normalizeAndApplySuffixOrSanitizer(
value: any, suffixOrSanitizer: SanitizerFn | string | undefined | null): string|null|undefined| value: any, suffixOrSanitizer: SanitizerFn | string | undefined | null): string|null|undefined|
boolean { boolean {
if (value === null || value === undefined) { if (value == null /** || value === undefined */) {
// do nothing // do nothing
} else if (typeof suffixOrSanitizer === 'function') { } else if (typeof suffixOrSanitizer === 'function') {
// sanitize the value. // sanitize the value.

View File

@ -5,7 +5,7 @@
* Use of this source code is governed by an MIT-style license that can be * 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 * found in the LICENSE file at https://angular.io/license
*/ */
import {ArrayMap} from '../../util/array_utils'; import {KeyValueArray} from '../../util/array_utils';
import {TStylingRange} from '../interfaces/styling'; import {TStylingRange} from '../interfaces/styling';
import {DirectiveDef} from './definition'; import {DirectiveDef} from './definition';
@ -498,7 +498,7 @@ export interface TNode {
styles: string|null; styles: string|null;
/** /**
* An `ArrayMap` version of residual `styles`. * A `KeyValueArray` version of residual `styles`.
* *
* When there are styling instructions than each instruction stores the static styling * When there are styling instructions than each instruction stores the static styling
* which is of lower priority than itself. This means that there may be a higher priority styling * which is of lower priority than itself. This means that there may be a higher priority styling
@ -522,9 +522,9 @@ export interface TNode {
* *
* - `undefined': not initialized. * - `undefined': not initialized.
* - `null`: initialized but `styles` is `null` * - `null`: initialized but `styles` is `null`
* - `ArrayMap`: parsed version of `styles`. * - `KeyValueArray`: parsed version of `styles`.
*/ */
residualStyles: ArrayMap<any>|undefined|null; residualStyles: KeyValueArray<any>|undefined|null;
/** /**
* A collection of all class static values for an element. * A collection of all class static values for an element.
@ -536,15 +536,15 @@ export interface TNode {
classes: string|null; classes: string|null;
/** /**
* An `ArrayMap` version of residual `classes`. * A `KeyValueArray` version of residual `classes`.
* *
* Same as `TNode.residualStyles` but for classes. * Same as `TNode.residualStyles` but for classes.
* *
* - `undefined': not initialized. * - `undefined': not initialized.
* - `null`: initialized but `classes` is `null` * - `null`: initialized but `classes` is `null`
* - `ArrayMap`: parsed version of `S`. * - `KeyValueArray`: parsed version of `classes`.
*/ */
residualClasses: ArrayMap<any>|undefined|null; residualClasses: KeyValueArray<any>|undefined|null;
/** /**
* Stores the head/tail index of the class bindings. * Stores the head/tail index of the class bindings.

View File

@ -6,7 +6,7 @@
* found in the LICENSE file at https://angular.io/license * found in the LICENSE file at https://angular.io/license
*/ */
import {ArrayMap} from '../../util/array_utils'; import {KeyValueArray} from '../../util/array_utils';
import {assertNumber, assertNumberInRange} from '../../util/assert'; import {assertNumber, assertNumberInRange} from '../../util/assert';
/** /**
@ -32,7 +32,7 @@ export type TStylingKeyPrimitive = string | null | false;
/** /**
* Store the static values for the styling binding. * Store the static values for the styling binding.
* *
* The `TStylingStatic` is just `ArrayMap` where key `""` (stored at location 0) contains the * The `TStylingStatic` is just `KeyValueArray` where key `""` (stored at location 0) contains the
* `TStylingKey` (stored at location 1). In other words this wraps the `TStylingKey` such that the * `TStylingKey` (stored at location 1). In other words this wraps the `TStylingKey` such that the
* `""` contains the wrapped value. * `""` contains the wrapped value.
* *
@ -89,7 +89,7 @@ export type TStylingKeyPrimitive = string | null | false;
* *
* This means that it is safe to add class. * This means that it is safe to add class.
*/ */
export interface TStylingStatic extends ArrayMap<any> {} export interface TStylingStatic extends KeyValueArray<any> {}
/** /**
* This is a branded number which contains previous and next index. * This is a branded number which contains previous and next index.

View File

@ -915,14 +915,15 @@ function applyContainer(
* @param isClassBased `true` if it should be written to `class` (`false` to write to `style`) * @param isClassBased `true` if it should be written to `class` (`false` to write to `style`)
* @param rNode The Node to write to. * @param rNode The Node to write to.
* @param prop Property to write to. This would be the class/style name. * @param prop Property to write to. This would be the class/style name.
* @param value Value to wiret. If `null`/`undefined`/`false` this is consider a remove (set/add * @param value Value to write. If `null`/`undefined`/`false` this is considered a remove (set/add
* otherwise). * otherwise).
*/ */
export function applyStyling( export function applyStyling(
renderer: Renderer3, isClassBased: boolean, rNode: RElement, prop: string, value: any) { renderer: Renderer3, isClassBased: boolean, rNode: RElement, prop: string, value: any) {
const isProcedural = isProceduralRenderer(renderer); const isProcedural = isProceduralRenderer(renderer);
if (isClassBased) { if (isClassBased) {
if (!value) { // We actually want JS falseness here // We actually want JS true/false here because any truthy value should add the class
if (!value) {
ngDevMode && ngDevMode.rendererRemoveClass++; ngDevMode && ngDevMode.rendererRemoveClass++;
if (isProcedural) { if (isProcedural) {
(renderer as Renderer2).removeClass(rNode, prop); (renderer as Renderer2).removeClass(rNode, prop);
@ -942,7 +943,7 @@ export function applyStyling(
// TODO(misko): Can't import RendererStyleFlags2.DashCase as it causes imports to be resolved in // TODO(misko): Can't import RendererStyleFlags2.DashCase as it causes imports to be resolved in
// different order which causes failures. Using direct constant as workaround for now. // different order which causes failures. Using direct constant as workaround for now.
const flags = prop.indexOf('-') == -1 ? undefined : 2 /* RendererStyleFlags2.DashCase */; const flags = prop.indexOf('-') == -1 ? undefined : 2 /* RendererStyleFlags2.DashCase */;
if (value === null || value === undefined) { if (value == null /** || value === undefined */) {
ngDevMode && ngDevMode.rendererRemoveStyle++; ngDevMode && ngDevMode.rendererRemoveStyle++;
if (isProcedural) { if (isProcedural) {
(renderer as Renderer2).removeStyle(rNode, prop, flags); (renderer as Renderer2).removeStyle(rNode, prop, flags);

View File

@ -240,21 +240,6 @@ export function getLView(): LView {
return lFrame === null ? null ! : lFrame.lView; return lFrame === null ? null ! : lFrame.lView;
} }
/**
* Sets the active directive host element and resets the directive id value
* (when the provided elementIndex value has changed).
*
* @param elementIndex the element index value for the host element where
* the directive/component instance lives
*/
export function setActiveHostElement(elementIndex: number) {
setSelectedIndex(elementIndex);
}
export function clearActiveHostElement() {
setSelectedIndex(-1);
}
/** /**
* Restores `contextViewData` to the given OpaqueViewState instance. * Restores `contextViewData` to the given OpaqueViewState instance.
* *
@ -562,11 +547,3 @@ export function getCurrentStyleSanitizer() {
const lFrame = instructionState.lFrame; const lFrame = instructionState.lFrame;
return lFrame === null ? null : lFrame.currentSanitizer; return lFrame === null ? null : lFrame.currentSanitizer;
} }
/**
* Used for encoding both Class and Style index into `LFrame.stylingBindingChanged`.
*/
const enum BindingChanged {
CLASS_SHIFT = 16,
STYLE_MASK = 0xFFFF,
}

View File

@ -6,7 +6,7 @@
* found in the LICENSE file at https://angular.io/license * found in the LICENSE file at https://angular.io/license
*/ */
import {ArrayMap, arrayMapIndexOf} from '../../util/array_utils'; import {KeyValueArray, keyValueArrayIndexOf} from '../../util/array_utils';
import {assertDataInRange, assertEqual, assertNotEqual} from '../../util/assert'; import {assertDataInRange, assertEqual, assertNotEqual} from '../../util/assert';
import {assertFirstUpdatePass} from '../assert'; import {assertFirstUpdatePass} from '../assert';
import {TNode} from '../interfaces/node'; import {TNode} from '../interfaces/node';
@ -204,10 +204,11 @@ export function insertTStylingBinding(
let tStylingKey: TStylingKeyPrimitive; let tStylingKey: TStylingKeyPrimitive;
if (Array.isArray(tStylingKeyWithStatic)) { if (Array.isArray(tStylingKeyWithStatic)) {
// We are case when the `TStylingKey` contains static fields as well. // We are case when the `TStylingKey` contains static fields as well.
const staticArrayMap = tStylingKeyWithStatic as ArrayMap<any>; const staticKeyValueArray = tStylingKeyWithStatic as KeyValueArray<any>;
tStylingKey = staticArrayMap[1]; // unwrap. tStylingKey = staticKeyValueArray[1]; // unwrap.
// We need to check if our key is present in the static so that we can mark it as duplicate. // We need to check if our key is present in the static so that we can mark it as duplicate.
if (tStylingKey === null || arrayMapIndexOf(staticArrayMap, tStylingKey as string) > 0) { if (tStylingKey === null ||
keyValueArrayIndexOf(staticKeyValueArray, tStylingKey as string) > 0) {
// tStylingKey is present in the statics, need to mark it as duplicate. // tStylingKey is present in the statics, need to mark it as duplicate.
isKeyDuplicateOfStatic = true; isKeyDuplicateOfStatic = true;
} }
@ -292,7 +293,7 @@ function markDuplicateOfResidualStyling(
tNode: TNode, tStylingKey: TStylingKey, tData: TData, index: number, isClassBinding: boolean) { tNode: TNode, tStylingKey: TStylingKey, tData: TData, index: number, isClassBinding: boolean) {
const residual = isClassBinding ? tNode.residualClasses : tNode.residualStyles; const residual = isClassBinding ? tNode.residualClasses : tNode.residualStyles;
if (residual != null /* or undefined */ && typeof tStylingKey == 'string' && if (residual != null /* or undefined */ && typeof tStylingKey == 'string' &&
arrayMapIndexOf(residual, tStylingKey) >= 0) { keyValueArrayIndexOf(residual, tStylingKey) >= 0) {
// We have duplicate in the residual so mark ourselves as duplicate. // We have duplicate in the residual so mark ourselves as duplicate.
tData[index + 1] = setTStylingRangeNextDuplicate(tData[index + 1] as TStylingRange); tData[index + 1] = setTStylingRangeNextDuplicate(tData[index + 1] as TStylingRange);
} }
@ -418,9 +419,10 @@ function isStylingMatch(tStylingKeyCursor: TStylingKey, tStylingKey: TStylingKey
) { ) {
return true; return true;
} else if (Array.isArray(tStylingKeyCursor) && typeof tStylingKey === 'string') { } else if (Array.isArray(tStylingKeyCursor) && typeof tStylingKey === 'string') {
// if we did not find a match, but `tStylingKeyCursor` is `ArrayMap` that means cursor has // if we did not find a match, but `tStylingKeyCursor` is `KeyValueArray` that means cursor has
// statics and we need to check those as well. // statics and we need to check those as well.
return arrayMapIndexOf(tStylingKeyCursor, tStylingKey) >= 0; // see if we are matching the key return keyValueArrayIndexOf(tStylingKeyCursor, tStylingKey) >=
0; // see if we are matching the key
} }
return false; return false;
} }

View File

@ -105,8 +105,6 @@ export function arraySplice(array: any[], index: number, count: number): void {
* removed. This causes memory pressure and slows down code when most of the time we don't * removed. This causes memory pressure and slows down code when most of the time we don't
* care about the deleted items array. * care about the deleted items array.
* *
* https://jsperf.com/fast-array-splice (About 20x faster)
*
* @param array Array to splice. * @param array Array to splice.
* @param index Index in array where the `value` should be added. * @param index Index in array where the `value` should be added.
* @param value Value to add to array. * @param value Value to add to array.
@ -129,8 +127,6 @@ export function arrayInsert(array: any[], index: number, value: any): void {
* removed. This causes memory pressure and slows down code when most of the time we don't * removed. This causes memory pressure and slows down code when most of the time we don't
* care about the deleted items array. * care about the deleted items array.
* *
* https://jsperf.com/fast-array-splice (About 20x faster)
*
* @param array Array to splice. * @param array Array to splice.
* @param index Index in array where the `value` should be added. * @param index Index in array where the `value` should be added.
* @param value1 Value to add to array. * @param value1 Value to add to array.
@ -221,35 +217,36 @@ export function arrayIndexOfSorted(array: string[], value: string): number {
/** /**
* `ArrayMap` is an array where even positions contain keys and odd positions contain values. * `KeyValueArray` is an array where even positions contain keys and odd positions contain values.
* *
* `ArrayMap` provides a very efficient way of iterating over its contents. For small * `KeyValueArray` provides a very efficient way of iterating over its contents. For small
* sets (~10) the cost of binary searching an `ArrayMap` has about the same performance * sets (~10) the cost of binary searching an `KeyValueArray` has about the same performance
* characteristics that of a `Map` with significantly better memory footprint. * characteristics that of a `Map` with significantly better memory footprint.
* *
* If used as a `Map` the keys are stored in alphabetical order so that they can be binary searched * If used as a `Map` the keys are stored in alphabetical order so that they can be binary searched
* for retrieval. * for retrieval.
* *
* See: `arrayMapSet`, `arrayMapGet`, `arrayMapIndexOf`, `arrayMapDelete`. * See: `keyValueArraySet`, `keyValueArrayGet`, `keyValueArrayIndexOf`, `keyValueArrayDelete`.
*/ */
export interface ArrayMap<VALUE> extends Array<VALUE|string> { __brand__: 'array-map'; } export interface KeyValueArray<VALUE> extends Array<VALUE|string> { __brand__: 'array-map'; }
/** /**
* Set a `value` for a `key`. * Set a `value` for a `key`.
* *
* @param arrayMap to modify. * @param keyValueArray to modify.
* @param key The key to locate or create. * @param key The key to locate or create.
* @param value The value to set for a `key`. * @param value The value to set for a `key`.
* @returns index (always even) of where the value vas set. * @returns index (always even) of where the value vas set.
*/ */
export function arrayMapSet<V>(arrayMap: ArrayMap<V>, key: string, value: V): number { export function keyValueArraySet<V>(
let index = arrayMapIndexOf(arrayMap, key); keyValueArray: KeyValueArray<V>, key: string, value: V): number {
let index = keyValueArrayIndexOf(keyValueArray, key);
if (index >= 0) { if (index >= 0) {
// if we found it set it. // if we found it set it.
arrayMap[index | 1] = value; keyValueArray[index | 1] = value;
} else { } else {
index = ~index; index = ~index;
arrayInsert2(arrayMap, index, key, value); arrayInsert2(keyValueArray, index, key, value);
} }
return index; return index;
} }
@ -257,15 +254,15 @@ export function arrayMapSet<V>(arrayMap: ArrayMap<V>, key: string, value: V): nu
/** /**
* Retrieve a `value` for a `key` (on `undefined` if not found.) * Retrieve a `value` for a `key` (on `undefined` if not found.)
* *
* @param arrayMap to search. * @param keyValueArray to search.
* @param key The key to locate. * @param key The key to locate.
* @return The `value` stored at the `key` location or `undefined if not found. * @return The `value` stored at the `key` location or `undefined if not found.
*/ */
export function arrayMapGet<V>(arrayMap: ArrayMap<V>, key: string): V|undefined { export function keyValueArrayGet<V>(keyValueArray: KeyValueArray<V>, key: string): V|undefined {
const index = arrayMapIndexOf(arrayMap, key); const index = keyValueArrayIndexOf(keyValueArray, key);
if (index >= 0) { if (index >= 0) {
// if we found it retrieve it. // if we found it retrieve it.
return arrayMap[index | 1] as V; return keyValueArray[index | 1] as V;
} }
return undefined; return undefined;
} }
@ -273,32 +270,32 @@ export function arrayMapGet<V>(arrayMap: ArrayMap<V>, key: string): V|undefined
/** /**
* Retrieve a `key` index value in the array or `-1` if not found. * Retrieve a `key` index value in the array or `-1` if not found.
* *
* @param arrayMap to search. * @param keyValueArray to search.
* @param key The key to locate. * @param key The key to locate.
* @returns index of where the key is (or should have been.) * @returns index of where the key is (or should have been.)
* - positive (even) index if key found. * - positive (even) index if key found.
* - negative index if key not found. (`~index` (even) to get the index where it should have * - negative index if key not found. (`~index` (even) to get the index where it should have
* been inserted.) * been inserted.)
*/ */
export function arrayMapIndexOf<V>(arrayMap: ArrayMap<V>, key: string): number { export function keyValueArrayIndexOf<V>(keyValueArray: KeyValueArray<V>, key: string): number {
return _arrayIndexOfSorted(arrayMap as string[], key, 1); return _arrayIndexOfSorted(keyValueArray as string[], key, 1);
} }
/** /**
* Delete a `key` (and `value`) from the `ArrayMap`. * Delete a `key` (and `value`) from the `KeyValueArray`.
* *
* @param arrayMap to modify. * @param keyValueArray to modify.
* @param key The key to locate or delete (if exist). * @param key The key to locate or delete (if exist).
* @returns index of where the key was (or should have been.) * @returns index of where the key was (or should have been.)
* - positive (even) index if key found and deleted. * - positive (even) index if key found and deleted.
* - negative index if key not found. (`~index` (even) to get the index where it should have * - negative index if key not found. (`~index` (even) to get the index where it should have
* been.) * been.)
*/ */
export function arrayMapDelete<V>(arrayMap: ArrayMap<V>, key: string): number { export function keyValueArrayDelete<V>(keyValueArray: KeyValueArray<V>, key: string): number {
const index = arrayMapIndexOf(arrayMap, key); const index = keyValueArrayIndexOf(keyValueArray, key);
if (index >= 0) { if (index >= 0) {
// if we found it remove it. // if we found it remove it.
arraySplice(arrayMap, index, 2); arraySplice(keyValueArray, index, 2);
} }
return index; return index;
} }

View File

@ -93,7 +93,7 @@ describe('host bindings', () => {
* - That executes a **different template**, that has host bindings * - That executes a **different template**, that has host bindings
* - this executes `setHostBindings` * - this executes `setHostBindings`
* - Inside of `setHostBindings` we are currently updating the selected index **global * - Inside of `setHostBindings` we are currently updating the selected index **global
* state** via `setActiveHostElement`. * state** via `setSelectedIndex`.
* 3. We attempt to update the next property in the **original template**. * 3. We attempt to update the next property in the **original template**.
* - But the selected index has been altered, and we get errors. * - But the selected index has been altered, and we get errors.
*/ */

View File

@ -3,7 +3,7 @@
## `js_expected_symbol_test` ## `js_expected_symbol_test`
This folder contains tests which assert that most of the code is tree shaken away. This folder contains tests which assert that most of the code is tree shaken away.
This is asserted by keeping gold files of all symbols which are expected to be retained. This is asserted by keeping gold files of all symbols which are expected to be retained.
When doing renaming it is often necessary to update the gold files, to do so use this commands: When doing renaming it is often necessary to update the gold files, to do so use these commands:
``` ```
yarn bazel run --config=ivy //packages/core/test/bundling/cyclic_import:symbol_test.accept yarn bazel run --config=ivy //packages/core/test/bundling/cyclic_import:symbol_test.accept

View File

@ -185,9 +185,6 @@
{ {
"name": "classIndexOf" "name": "classIndexOf"
}, },
{
"name": "clearActiveHostElement"
},
{ {
"name": "computeStaticStyling" "name": "computeStaticStyling"
}, },
@ -572,9 +569,6 @@
{ {
"name": "selectIndexInternal" "name": "selectIndexInternal"
}, },
{
"name": "setActiveHostElement"
},
{ {
"name": "setBindingIndex" "name": "setBindingIndex"
}, },

View File

@ -164,9 +164,6 @@
{ {
"name": "callHooks" "name": "callHooks"
}, },
{
"name": "clearActiveHostElement"
},
{ {
"name": "computeStaticStyling" "name": "computeStaticStyling"
}, },
@ -446,9 +443,6 @@
{ {
"name": "selectIndexInternal" "name": "selectIndexInternal"
}, },
{
"name": "setActiveHostElement"
},
{ {
"name": "setBindingIndex" "name": "setBindingIndex"
}, },

View File

@ -344,15 +344,6 @@
{ {
"name": "arrayInsert2" "name": "arrayInsert2"
}, },
{
"name": "arrayMapGet"
},
{
"name": "arrayMapIndexOf"
},
{
"name": "arrayMapSet"
},
{ {
"name": "assertTemplate" "name": "assertTemplate"
}, },
@ -395,9 +386,6 @@
{ {
"name": "cleanUpView" "name": "cleanUpView"
}, },
{
"name": "clearActiveHostElement"
},
{ {
"name": "collectNativeNodes" "name": "collectNativeNodes"
}, },
@ -902,6 +890,15 @@
{ {
"name": "iterateListLike" "name": "iterateListLike"
}, },
{
"name": "keyValueArrayGet"
},
{
"name": "keyValueArrayIndexOf"
},
{
"name": "keyValueArraySet"
},
{ {
"name": "leaveDI" "name": "leaveDI"
}, },
@ -1079,9 +1076,6 @@
{ {
"name": "selectIndexInternal" "name": "selectIndexInternal"
}, },
{
"name": "setActiveHostElement"
},
{ {
"name": "setBindingIndex" "name": "setBindingIndex"
}, },
@ -1155,7 +1149,7 @@
"name": "stringifyForError" "name": "stringifyForError"
}, },
{ {
"name": "stylingPropertyFirstUpdatePass" "name": "stylingFirstUpdatePass"
}, },
{ {
"name": "syncViewWithBlueprint" "name": "syncViewWithBlueprint"

View File

@ -12,7 +12,7 @@ import {TNodeType} from '@angular/core/src/render3/interfaces/node';
import {LView, TView, TViewType} from '@angular/core/src/render3/interfaces/view'; import {LView, TView, TViewType} from '@angular/core/src/render3/interfaces/view';
import {enterView, leaveView} from '@angular/core/src/render3/state'; import {enterView, leaveView} from '@angular/core/src/render3/state';
import {insertTStylingBinding} from '@angular/core/src/render3/styling/style_binding_list'; import {insertTStylingBinding} from '@angular/core/src/render3/styling/style_binding_list';
import {ArrayMap} from '@angular/core/src/util/array_utils'; import {KeyValueArray} from '@angular/core/src/util/array_utils';
describe('lView_debug', () => { describe('lView_debug', () => {
@ -36,14 +36,14 @@ describe('lView_debug', () => {
}); });
it('should decode static styling', () => { it('should decode static styling', () => {
tNode.residualStyles = ['color', 'blue'] as ArrayMap<any>; tNode.residualStyles = ['color', 'blue'] as KeyValueArray<any>;
tNode.residualClasses = ['STATIC', true] as ArrayMap<any>; tNode.residualClasses = ['STATIC', true] as KeyValueArray<any>;
expect(tNode.styleBindings_).toEqual([['color', 'blue'] as ArrayMap<any>]); expect(tNode.styleBindings_).toEqual([['color', 'blue'] as KeyValueArray<any>]);
expect(tNode.classBindings_).toEqual([['STATIC', true] as ArrayMap<any>]); expect(tNode.classBindings_).toEqual([['STATIC', true] as KeyValueArray<any>]);
}); });
it('should decode no-template property binding', () => { it('should decode no-template property binding', () => {
tNode.residualClasses = ['STATIC', true] as ArrayMap<any>; tNode.residualClasses = ['STATIC', true] as KeyValueArray<any>;
insertTStylingBinding(tView.data, tNode, 'CLASS', 2, true, true); insertTStylingBinding(tView.data, tNode, 'CLASS', 2, true, true);
insertTStylingBinding(tView.data, tNode, 'color', 4, true, false); insertTStylingBinding(tView.data, tNode, 'color', 4, true, false);
@ -69,12 +69,12 @@ describe('lView_debug', () => {
prevIndex: 0, prevIndex: 0,
nextIndex: 0, nextIndex: 0,
}, },
['STATIC', true] as ArrayMap<any> ['STATIC', true] as KeyValueArray<any>
]); ]);
}); });
it('should decode template and directive property binding', () => { it('should decode template and directive property binding', () => {
tNode.residualClasses = ['STATIC', true] as ArrayMap<any>; tNode.residualClasses = ['STATIC', true] as KeyValueArray<any>;
insertTStylingBinding(tView.data, tNode, 'CLASS', 2, false, true); insertTStylingBinding(tView.data, tNode, 'CLASS', 2, false, true);
insertTStylingBinding(tView.data, tNode, 'color', 4, false, false); insertTStylingBinding(tView.data, tNode, 'color', 4, false, false);
@ -100,7 +100,7 @@ describe('lView_debug', () => {
prevIndex: 0, prevIndex: 0,
nextIndex: 0, nextIndex: 0,
}, },
['STATIC', true] as ArrayMap<any> ['STATIC', true] as KeyValueArray<any>
]); ]);
insertTStylingBinding(tView.data, tNode, null, 6, true, true); insertTStylingBinding(tView.data, tNode, null, 6, true, true);
@ -146,7 +146,7 @@ describe('lView_debug', () => {
prevIndex: 6, prevIndex: 6,
nextIndex: 0, nextIndex: 0,
}, },
['STATIC', true] as ArrayMap<any> ['STATIC', true] as KeyValueArray<any>
]); ]);
}); });
}); });

View File

@ -8,7 +8,7 @@
import {DirectiveDef} from '@angular/core/src/render3'; import {DirectiveDef} from '@angular/core/src/render3';
import {ɵɵdefineDirective} from '@angular/core/src/render3/definition'; import {ɵɵdefineDirective} from '@angular/core/src/render3/definition';
import {classStringParser, initializeStylingStaticArrayMap, styleStringParser, toStylingArrayMap, ɵɵclassProp, ɵɵstyleMap, ɵɵstyleProp, ɵɵstyleSanitizer} from '@angular/core/src/render3/instructions/styling'; import {classStringParser, styleStringParser, toStylingKeyValueArray, ɵɵclassProp, ɵɵstyleMap, ɵɵstyleProp, ɵɵstyleSanitizer} from '@angular/core/src/render3/instructions/styling';
import {AttributeMarker, TAttributes, TDirectiveDefs} from '@angular/core/src/render3/interfaces/node'; import {AttributeMarker, TAttributes, TDirectiveDefs} from '@angular/core/src/render3/interfaces/node';
import {StylingRange, TStylingKey, TStylingRange, getTStylingRangeNext, getTStylingRangeNextDuplicate, getTStylingRangePrev, getTStylingRangePrevDuplicate, setTStylingRangeNext, setTStylingRangePrev, toTStylingRange} from '@angular/core/src/render3/interfaces/styling'; import {StylingRange, TStylingKey, TStylingRange, getTStylingRangeNext, getTStylingRangeNextDuplicate, getTStylingRangePrev, getTStylingRangePrevDuplicate, setTStylingRangeNext, setTStylingRangePrev, toTStylingRange} from '@angular/core/src/render3/interfaces/styling';
import {HEADER_OFFSET, TVIEW} from '@angular/core/src/render3/interfaces/view'; import {HEADER_OFFSET, TVIEW} from '@angular/core/src/render3/interfaces/view';
@ -16,7 +16,7 @@ import {getLView, leaveView, setBindingRootForHostBindings} from '@angular/core/
import {getNativeByIndex} from '@angular/core/src/render3/util/view_utils'; import {getNativeByIndex} from '@angular/core/src/render3/util/view_utils';
import {bypassSanitizationTrustStyle} from '@angular/core/src/sanitization/bypass'; import {bypassSanitizationTrustStyle} from '@angular/core/src/sanitization/bypass';
import {ɵɵsanitizeStyle} from '@angular/core/src/sanitization/sanitization'; import {ɵɵsanitizeStyle} from '@angular/core/src/sanitization/sanitization';
import {arrayMapSet} from '@angular/core/src/util/array_utils'; import {keyValueArraySet} from '@angular/core/src/util/array_utils';
import {ngDevModeResetPerfCounters} from '@angular/core/src/util/ng_dev_mode'; import {ngDevModeResetPerfCounters} from '@angular/core/src/util/ng_dev_mode';
import {getElementClasses, getElementStyles} from '@angular/core/testing/src/styling'; import {getElementClasses, getElementStyles} from '@angular/core/testing/src/styling';
import {expect} from '@angular/core/testing/src/testing_internal'; import {expect} from '@angular/core/testing/src/testing_internal';
@ -268,31 +268,6 @@ describe('styling', () => {
expect(div.style.getPropertyValue('background')).toContain('url("javascript:/trusted")'); expect(div.style.getPropertyValue('background')).toContain('url("javascript:/trusted")');
}); });
}); });
describe('populateStylingStaticArrayMap', () => {
it('should initialize to null if no mergedAttrs', () => {
const tNode = getLView()[TVIEW].firstChild !;
expect(tNode.residualStyles).toEqual(undefined);
expect(tNode.residualClasses).toEqual(undefined);
initializeStylingStaticArrayMap(tNode);
expect(tNode.residualStyles).toEqual(null);
expect(tNode.residualClasses).toEqual(null);
});
it('should initialize from mergeAttrs', () => {
const tNode = getLView()[TVIEW].firstChild !;
expect(tNode.residualStyles).toEqual(undefined);
expect(tNode.residualClasses).toEqual(undefined);
tNode.mergedAttrs = [
'ignore', 'value', //
AttributeMarker.Classes, 'foo', 'bar', //
AttributeMarker.Styles, 'width', '0', 'color', 'red', //
];
initializeStylingStaticArrayMap(tNode);
expect(tNode.residualClasses).toEqual(['bar', true, 'foo', true] as any);
expect(tNode.residualStyles).toEqual(['color', 'red', 'width', '0'] as any);
});
});
}); });
describe('static', () => { describe('static', () => {
@ -428,53 +403,54 @@ describe('styling', () => {
describe('toStylingArray', () => { describe('toStylingArray', () => {
describe('falsy', () => { describe('falsy', () => {
it('should return empty ArrayMap', () => { it('should return empty KeyValueArray', () => {
expect(toStylingArrayMap(arrayMapSet, null !, '')).toEqual([] as any); expect(toStylingKeyValueArray(keyValueArraySet, null !, '')).toEqual([] as any);
expect(toStylingArrayMap(arrayMapSet, null !, null)).toEqual([] as any); expect(toStylingKeyValueArray(keyValueArraySet, null !, null)).toEqual([] as any);
expect(toStylingArrayMap(arrayMapSet, null !, undefined)).toEqual([] as any); expect(toStylingKeyValueArray(keyValueArraySet, null !, undefined)).toEqual([] as any);
expect(toStylingArrayMap(arrayMapSet, null !, [])).toEqual([] as any); expect(toStylingKeyValueArray(keyValueArraySet, null !, [])).toEqual([] as any);
expect(toStylingArrayMap(arrayMapSet, null !, {})).toEqual([] as any); expect(toStylingKeyValueArray(keyValueArraySet, null !, {})).toEqual([] as any);
}); });
describe('string', () => { describe('string', () => {
it('should parse classes', () => { it('should parse classes', () => {
expect(toStylingArrayMap(arrayMapSet, classStringParser, ' ')).toEqual([] as any); expect(toStylingKeyValueArray(keyValueArraySet, classStringParser, ' ')).toEqual([
expect(toStylingArrayMap(arrayMapSet, classStringParser, ' X A ')).toEqual([ ] as any);
expect(toStylingKeyValueArray(keyValueArraySet, classStringParser, ' X A ')).toEqual([
'A', true, 'X', true 'A', true, 'X', true
] as any); ] as any);
}); });
it('should parse styles', () => { it('should parse styles', () => {
expect(toStylingArrayMap(arrayMapSet, styleStringParser, ' ')).toEqual([] as any); expect(toStylingKeyValueArray(keyValueArraySet, styleStringParser, ' ')).toEqual([
expect(toStylingArrayMap(arrayMapSet, styleStringParser, 'B:b;A:a')).toEqual([ ] as any);
expect(toStylingKeyValueArray(keyValueArraySet, styleStringParser, 'B:b;A:a')).toEqual([
'A', 'a', 'B', 'b' 'A', 'a', 'B', 'b'
] as any); ] as any);
}); });
}); });
describe('array', () => { describe('array', () => {
it('should parse', () => { it('should parse', () => {
expect(toStylingArrayMap(arrayMapSet, null !, ['X', 'A'])).toEqual([ expect(toStylingKeyValueArray(keyValueArraySet, null !, ['X', 'A'])).toEqual([
'A', true, 'X', true 'A', true, 'X', true
] as any); ] as any);
}); });
}); });
describe('object', () => { describe('object', () => {
it('should parse', () => { it('should parse', () => {
expect(toStylingArrayMap(arrayMapSet, null !, {X: 'x', A: 'a'})).toEqual([ expect(toStylingKeyValueArray(keyValueArraySet, null !, {X: 'x', A: 'a'})).toEqual([
'A', 'a', 'X', 'x' 'A', 'a', 'X', 'x'
] as any); ] as any);
}); });
}); });
describe('Map', () => { describe('Map', () => {
it('should parse', () => { it('should parse', () => {
expect(toStylingArrayMap( expect(toStylingKeyValueArray(
arrayMapSet, null !, new Map<string, string>([['X', 'x'], ['A', 'a']]))) keyValueArraySet, null !, new Map<string, string>([['X', 'x'], ['A', 'a']])))
.toEqual(['A', 'a', 'X', 'x'] as any); .toEqual(['A', 'a', 'X', 'x'] as any);
}); });
}); });
describe('Iterable', () => { describe('Iterable', () => {
it('should parse', () => { it('should parse', () => {
expect(toStylingArrayMap(arrayMapSet, null !, new Set<string>(['X', 'A']))).toEqual([ expect(toStylingKeyValueArray(keyValueArraySet, null !, new Set<string>(['X', 'A'])))
'A', true, 'X', true .toEqual(['A', true, 'X', true] as any);
] as any);
}); });
}); });
}); });

View File

@ -6,7 +6,7 @@
* found in the LICENSE file at https://angular.io/license * found in the LICENSE file at https://angular.io/license
*/ */
import {ArrayMap, arrayIndexOfSorted, arrayInsert, arrayInsert2, arrayInsertSorted, arrayMapDelete, arrayMapGet, arrayMapIndexOf, arrayMapSet, arrayRemoveSorted, arraySplice, flatten} from '../../src/util/array_utils'; import {KeyValueArray, arrayIndexOfSorted, arrayInsert, arrayInsert2, arrayInsertSorted, arrayRemoveSorted, arraySplice, flatten, keyValueArrayDelete, keyValueArrayGet, keyValueArrayIndexOf, keyValueArraySet} from '../../src/util/array_utils';
describe('array_utils', () => { describe('array_utils', () => {
@ -140,38 +140,38 @@ describe('array_utils', () => {
}); });
}); });
describe('ArrayMap', () => { describe('KeyValueArray', () => {
it('should support basic operations', () => { it('should support basic operations', () => {
const map: ArrayMap<number> = [] as any; const map: KeyValueArray<number> = [] as any;
expect(arrayMapIndexOf(map, 'A')).toEqual(~0); expect(keyValueArrayIndexOf(map, 'A')).toEqual(~0);
expect(arrayMapSet(map, 'B', 1)).toEqual(0); expect(keyValueArraySet(map, 'B', 1)).toEqual(0);
expect(map).toEqual(['B', 1]); expect(map).toEqual(['B', 1]);
expect(arrayMapIndexOf(map, 'B')).toEqual(0); expect(keyValueArrayIndexOf(map, 'B')).toEqual(0);
expect(arrayMapSet(map, 'A', 0)).toEqual(0); expect(keyValueArraySet(map, 'A', 0)).toEqual(0);
expect(map).toEqual(['A', 0, 'B', 1]); expect(map).toEqual(['A', 0, 'B', 1]);
expect(arrayMapIndexOf(map, 'B')).toEqual(2); expect(keyValueArrayIndexOf(map, 'B')).toEqual(2);
expect(arrayMapIndexOf(map, 'AA')).toEqual(~2); expect(keyValueArrayIndexOf(map, 'AA')).toEqual(~2);
expect(arrayMapSet(map, 'C', 2)).toEqual(4); expect(keyValueArraySet(map, 'C', 2)).toEqual(4);
expect(map).toEqual(['A', 0, 'B', 1, 'C', 2]); expect(map).toEqual(['A', 0, 'B', 1, 'C', 2]);
expect(arrayMapGet(map, 'A')).toEqual(0); expect(keyValueArrayGet(map, 'A')).toEqual(0);
expect(arrayMapGet(map, 'B')).toEqual(1); expect(keyValueArrayGet(map, 'B')).toEqual(1);
expect(arrayMapGet(map, 'C')).toEqual(2); expect(keyValueArrayGet(map, 'C')).toEqual(2);
expect(arrayMapGet(map, 'AA')).toEqual(undefined); expect(keyValueArrayGet(map, 'AA')).toEqual(undefined);
expect(arrayMapSet(map, 'B', -1)).toEqual(2); expect(keyValueArraySet(map, 'B', -1)).toEqual(2);
expect(map).toEqual(['A', 0, 'B', -1, 'C', 2]); expect(map).toEqual(['A', 0, 'B', -1, 'C', 2]);
expect(arrayMapDelete(map, 'AA')).toEqual(~2); expect(keyValueArrayDelete(map, 'AA')).toEqual(~2);
expect(arrayMapDelete(map, 'B')).toEqual(2); expect(keyValueArrayDelete(map, 'B')).toEqual(2);
expect(map).toEqual(['A', 0, 'C', 2]); expect(map).toEqual(['A', 0, 'C', 2]);
expect(arrayMapDelete(map, 'A')).toEqual(0); expect(keyValueArrayDelete(map, 'A')).toEqual(0);
expect(map).toEqual(['C', 2]); expect(map).toEqual(['C', 2]);
expect(arrayMapDelete(map, 'C')).toEqual(0); expect(keyValueArrayDelete(map, 'C')).toEqual(0);
expect(map).toEqual([]); expect(map).toEqual([]);
}); });
}); });