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 {CONTEXT, HEADER_OFFSET, LView, LViewFlags, RootContext, RootContextFlags, TVIEW, TViewType} from './interfaces/view';
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 {setUpAttributes} from './util/attrs_utils';
import {publishDefaultGlobalUtils} from './util/global_utils';
@ -239,7 +239,7 @@ export function createRootComponent<T>(
if (tView.firstCreatePass &&
(componentDef.hostBindings !== null || componentDef.hostAttrs !== null)) {
const elementIndex = rootTNode.index - HEADER_OFFSET;
setActiveHostElement(elementIndex);
setSelectedIndex(elementIndex);
const rootTView = rootLView[TVIEW];
addHostBindingsToExpandoInstructions(rootTView, componentDef);

View File

@ -6,7 +6,7 @@
* 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 {interpolation1, interpolation2, interpolation3, interpolation4, interpolation5, interpolation6, interpolation7, interpolation8, interpolationV} from './interpolation';
import {checkStylingMap, classStringParser} from './styling';
@ -37,7 +37,7 @@ import {checkStylingMap, classStringParser} from './styling';
export function ɵɵclassMapInterpolate1(prefix: string, v0: any, suffix: string): void {
const lView = getLView();
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 {
const lView = getLView();
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 {
const lView = getLView();
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 {
const lView = getLView();
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 interpolatedValue =
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 interpolatedValue =
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 interpolatedValue =
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 interpolatedValue = interpolation8(
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 {
const lView = getLView();
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 {SchemaMetadata} from '../../core';
import {ArrayMap} from '../../util/array_utils';
import {KeyValueArray} from '../../util/array_utils';
import {assertDefined} from '../../util/assert';
import {createNamedArrayType} from '../../util/named_array_type';
import {initNgDevMode} from '../../util/ng_dev_mode';
@ -176,9 +176,9 @@ class TNode implements ITNode {
public parent: TElementNode|TContainerNode|null, //
public projection: number|(ITNode|RNode[])[]|null, //
public styles: string|null, //
public residualStyles: ArrayMap<any>|undefined|null, //
public residualStyles: KeyValueArray<any>|undefined|null, //
public classes: string|null, //
public residualClasses: ArrayMap<any>|undefined|null, //
public residualClasses: KeyValueArray<any>|undefined|null, //
public classBindings: TStylingRange, //
public styleBindings: TStylingRange, //
public directives: TDirectiveDefs|null, //
@ -241,7 +241,8 @@ class TNode implements ITNode {
export const 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 {
key: TStylingKey;
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 {assertNodeOfPossibleTypes} from '../node_assert';
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 {isAnimationProp, mergeHostAttrs} from '../util/attrs_utils';
import {INTERPOLATION_DELIMITER, renderStringify, stringifyForError} from '../util/misc_utils';
import {getLViewParent} from '../util/view_traversal_utils';
import {getComponentLViewByIndex, getNativeByIndex, getNativeByTNode, getTNode, isCreationMode, readPatchedLView, resetPreOrderHookFlags, viewAttachedToChangeDetector} from '../util/view_utils';
import {selectIndexInternal} from './advance';
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))();
/**
* Process the `TVIew.expandoInstructions`. (Execute the `hostBindings`.)
* Process the `TView.expandoInstructions`. (Execute the `hostBindings`.)
*
* @param tView `TView` containing the `expandoInstructions`
* @param lView `LView` associated with the `TView`
@ -62,7 +63,7 @@ export function setHostBindingsByExecutingExpandoInstructions(tView: TView, lVie
let bindingRootIndex = tView.expandoStartIndex;
let currentDirectiveIndex = -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
// to iterate over the instructions.
// 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
// suffer from `-0` and it is faster/more compact.
currentElementIndex = 0 - instruction;
setActiveHostElement(currentElementIndex);
setSelectedIndex(currentElementIndex);
// Injector block and providers are taken into account.
const providerCount = (expandoInstructions[++i] as number);
@ -112,7 +113,7 @@ export function setHostBindingsByExecutingExpandoInstructions(tView: TView, lVie
}
}
} finally {
clearActiveHostElement();
setSelectedIndex(-1);
}
}
@ -528,7 +529,7 @@ function executeTemplate<T>(
lView: LView, templateFn: ComponentTemplate<T>, rf: RenderFlags, context: T) {
const prevSelectedIndex = getSelectedIndex();
try {
clearActiveHostElement();
setSelectedIndex(-1);
if (rf & RenderFlags.Update && lView.length > HEADER_OFFSET) {
// When we're updating, inherently select 0 so we don't
// 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 elementIndex = tNode.index - HEADER_OFFSET;
try {
setActiveHostElement(elementIndex);
setSelectedIndex(elementIndex);
for (let i = start; i < end; i++) {
const def = tView.data[i] as DirectiveDef<any>;
const directive = lView[i];
@ -1275,7 +1276,7 @@ function invokeDirectivesHostBindings(tView: TView, lView: LView, tNode: TNode)
}
}
} finally {
clearActiveHostElement();
setSelectedIndex(-1);
}
}

View File

@ -9,7 +9,7 @@
import {SafeValue, unwrapSafeValue} from '../../sanitization/bypass';
import {stylePropNeedsSanitization, ɵɵsanitizeStyle} from '../../sanitization/sanitization';
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 {EMPTY_ARRAY} from '../../util/empty';
import {concatStringsWithSpace, stringify} from '../../util/stringify';
@ -122,22 +122,22 @@ export function ɵɵclassProp(
export function ɵɵstyleMap(
styles: {[styleName: string]: any} | Map<string, string|number|null|undefined>| string |
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
* 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.
*/
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)) {
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(
classes: {[className: string]: boolean | undefined | null} |
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
* 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.
*/
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)) {
arrayMapSet(arrayMap, getLastParsedKey(text), true);
keyValueArraySet(keyValueArray, getLastParsedKey(text), true);
}
}
@ -199,7 +199,7 @@ export function checkStylingProperty(
// 2. one for the intermittent-value / TStylingRange
const bindingIndex = incrementBindingIndex(2);
if (tView.firstUpdatePass) {
stylingPropertyFirstUpdatePass(tView, prop, bindingIndex, isClassBased);
stylingFirstUpdatePass(tView, prop, bindingIndex, isClassBased);
}
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
@ -219,21 +219,26 @@ export function checkStylingProperty(
}
/**
* Common code between `ɵɵclassMap` and `ɵɵstyleMap`.
*
* @param tStylingMapKey See `STYLE_MAP_STYLING_KEY` and `CLASS_MAP_STYLING_KEY`.
* @param value binding value.
* @param isClassBased `true` if `class` change (`false` if `style`)
*/
* Common code between `ɵɵclassMap` and `ɵɵstyleMap`.
*
* @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 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`)
*/
export function checkStylingMap(
arrayMapSet: (arrayMap: ArrayMap<any>, key: string, value: any) => void,
stringParser: (styleArrayMap: ArrayMap<any>, text: string) => void, value: any|NO_CHANGE,
isClassBased: boolean): void {
keyValueArraySet: (keyValueArray: KeyValueArray<any>, key: string, value: any) => void,
stringParser: (styleKeyValueArray: KeyValueArray<any>, text: string) => void,
value: any|NO_CHANGE, isClassBased: boolean): void {
const lView = getLView();
const tView = lView[TVIEW];
const bindingIndex = incrementBindingIndex(2);
if (tView.firstUpdatePass) {
stylingPropertyFirstUpdatePass(tView, null, bindingIndex, isClassBased);
stylingFirstUpdatePass(tView, null, bindingIndex, isClassBased);
}
if (value !== NO_CHANGE && bindingUpdated(lView, bindingIndex, value)) {
// `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.
// 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
// tha 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
// the binding has removed it. This would confuse `[ngStyle]`/`[ngClass]` to do the wrong
// 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.
let staticPrefix = isClassBased ? tNode.classes : tNode.styles;
ngDevMode && isClassBased === false && staticPrefix !== null &&
@ -268,7 +273,7 @@ export function checkStylingMap(
} else {
updateStylingMap(
tView, tNode, lView, lView[RENDERER], lView[bindingIndex + 1],
lView[bindingIndex + 1] = toStylingArrayMap(arrayMapSet, stringParser, value),
lView[bindingIndex + 1] = toStylingKeyValueArray(keyValueArraySet, stringParser, value),
isClassBased, bindingIndex);
}
}
@ -290,12 +295,11 @@ function isInHostBindings(tView: TView, bindingIndex: number): boolean {
* using `insertTStylingBinding`.
*
* @param tView `TView` where the binding linked list will be stored.
* @param prop Property/key of the binding.
* @param suffix Optional suffix or Sanitization function.
* @param tStylingKey Property/key of the binding.
* @param bindingIndex Index of binding associated with the `prop`
* @param isClassBased `true` if `class` change (`false` if `style`)
*/
function stylingPropertyFirstUpdatePass(
function stylingFirstUpdatePass(
tView: TView, tStylingKey: TStylingKey, bindingIndex: number, isClassBased: boolean): void {
ngDevMode && assertFirstUpdatePass(tView);
const tData = tView.data;
@ -429,16 +433,17 @@ function setTemplateHeadTStylingKey(
tData[getTStylingRangePrev(bindings)] = tStylingKey;
}
function collectResidual(tNode: TNode, isClassBased: boolean): ArrayMap<any>|null {
let residual: ArrayMap<any>|null|undefined = undefined;
function collectResidual(tNode: TNode, isClassBased: boolean): KeyValueArray<any>|null {
let residual: KeyValueArray<any>|null|undefined = undefined;
const directives = tNode.directives;
if (directives) {
for (let i = directives[DirectiveDefs.STYLING_CURSOR] + 1; i < directives.length; i++) {
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)) {
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
* 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).
*
* 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
* 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.
*
* 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.
*
* @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
* `styleStringParser` and `classStringParser`
* @param value The value to parse/convert to `ArrayMap`
* @param value The value to parse/convert to `KeyValueArray`
*/
export function toStylingArrayMap(
arrayMapSet: (arrayMap: ArrayMap<any>, key: string, value: any) => void,
stringParser: (styleArrayMap: ArrayMap<any>, text: string) => void, value: string|string[]|
{[key: string]: any}|Map<any, any>|Set<any>|null|undefined): ArrayMap<any> {
if (value === null || value === undefined || value === '') return EMPTY_ARRAY as any;
const styleArrayMap: ArrayMap<any> = [] as any;
export function toStylingKeyValueArray(
keyValueArraySet: (keyValueArray: KeyValueArray<any>, key: string, value: any) => void,
stringParser: (styleKeyValueArray: KeyValueArray<any>, text: string) => void, value: string|
string[]|{[key: string]: any}|Map<any, any>|Set<any>|null|undefined): KeyValueArray<any> {
if (value == null /*|| value === undefined */ || value === '') return EMPTY_ARRAY as any;
const styleKeyValueArray: KeyValueArray<any> = [] as any;
if (Array.isArray(value)) {
for (let i = 0; i < value.length; i++) {
arrayMapSet(styleArrayMap, value[i], true);
keyValueArraySet(styleKeyValueArray, value[i], true);
}
} else if (typeof value === 'object') {
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) {
value.forEach((k) => arrayMapSet(styleArrayMap, k, true));
value.forEach((k) => keyValueArraySet(styleKeyValueArray, k, true));
} else {
for (const key in value) {
if (value.hasOwnProperty(key)) {
arrayMapSet(styleArrayMap, key, value[key]);
keyValueArraySet(styleKeyValueArray, key, value[key]);
}
}
}
} else if (typeof value === 'string') {
stringParser(styleArrayMap, value);
stringParser(styleKeyValueArray, value);
} 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.
*
* 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 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)) {
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`, `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
* `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.
*
* @param tView Associated `TView.data` contains the linked list of binding priorities.
* @param tNode `TNode` where the binding is located.
* @param lView `LView` contains the values associated with other styling binding at this `TNode`.
* @param renderer Renderer to use if any updates.
* @param oldArrayMap Previous value represented as `ArrayMap`
* @param newArrayMap Current value represented as `ArrayMap`
* @param oldKeyValueArray Previous value represented as `KeyValueArray`
* @param newKeyValueArray Current value represented as `KeyValueArray`
* @param isClassBased `true` if `class` (`false` if `style`)
* @param bindingIndex Binding index of the binding.
*/
function updateStylingMap(
tView: TView, tNode: TNode, lView: LView, renderer: Renderer3, oldArrayMap: ArrayMap<any>,
newArrayMap: ArrayMap<any>, isClassBased: boolean, bindingIndex: number) {
if (oldArrayMap as ArrayMap<any>| NO_CHANGE === NO_CHANGE) {
// ON first execution the oldArrayMap is NO_CHANGE => treat is as empty ArrayMap.
oldArrayMap = EMPTY_ARRAY as any;
tView: TView, tNode: TNode, lView: LView, renderer: Renderer3,
oldKeyValueArray: KeyValueArray<any>, newKeyValueArray: KeyValueArray<any>,
isClassBased: boolean, bindingIndex: number) {
if (oldKeyValueArray as KeyValueArray<any>| NO_CHANGE === NO_CHANGE) {
// On first execution the oldKeyValueArray is NO_CHANGE => treat it as empty KeyValueArray.
oldKeyValueArray = EMPTY_ARRAY as any;
}
let oldIndex = 0;
let newIndex = 0;
let oldKey: string|null = 0 < oldArrayMap.length ? oldArrayMap[0] : null;
let newKey: string|null = 0 < newArrayMap.length ? newArrayMap[0] : null;
let oldKey: string|null = 0 < oldKeyValueArray.length ? oldKeyValueArray[0] : null;
let newKey: string|null = 0 < newKeyValueArray.length ? newKeyValueArray[0] : null;
while (oldKey !== null || newKey !== null) {
ngDevMode && assertLessThan(oldIndex, 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 newValue = newIndex < newArrayMap.length ? newArrayMap[newIndex + 1] : undefined;
const oldValue =
oldIndex < oldKeyValueArray.length ? oldKeyValueArray[oldIndex + 1] : undefined;
const newValue =
newIndex < newKeyValueArray.length ? newKeyValueArray[newIndex + 1] : undefined;
let setKey: string|null = null;
let setValue: any = undefined;
if (oldKey === newKey) {
@ -637,11 +654,16 @@ function updateStylingMap(
setValue = newValue;
}
} 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;
setKey = oldKey;
} 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');
newIndex += 2;
setKey = newKey;
@ -650,8 +672,8 @@ function updateStylingMap(
if (setKey !== null) {
updateStyling(tView, tNode, lView, renderer, setKey, setValue, isClassBased, bindingIndex);
}
oldKey = oldIndex < oldArrayMap.length ? oldArrayMap[oldIndex] : null;
newKey = newIndex < newArrayMap.length ? newArrayMap[newIndex] : null;
oldKey = oldIndex < oldKeyValueArray.length ? oldKeyValueArray[oldIndex] : 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 renderer Renderer to use if any updates.
* @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 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.
* 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`.
* - 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
* `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.
* 1. The raw value which came from the application is stored at `index + 0` location. (This value
* 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
* to worry about sanitizing it later or keeping track of the sanitizer.
*
@ -731,32 +755,38 @@ function updateStyling(
function findStylingValue(
tData: TData, tNode: TNode | null, lView: LView, prop: string, index: number,
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;
while (index > 0) {
const rawKey = tData[index] as TStylingKey;
const containsStatics = Array.isArray(rawKey);
// Unwrap the key if we contain static values.
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;
if (containsStatics && !isStylingValuePresent(currentValue)) {
currentValue = arrayMapGet(rawKey as ArrayMap<any>, prop);
currentValue = keyValueArrayGet(rawKey as KeyValueArray<any>, prop);
}
if (isStylingValuePresent(currentValue)) {
value = currentValue;
if (tNode === null) {
if (isPrevDirection) {
return value;
}
}
const tRange = tData[index + 1] as TStylingRange;
index = tNode === null ? getTStylingRangePrev(tRange) : getTStylingRangeNext(tRange);
index = isPrevDirection ? getTStylingRangePrev(tRange) : getTStylingRangeNext(tRange);
}
if (tNode !== null) {
// in case where we are going in next direction AND we did not find anything, we need to
// consult residual styling
let residual = isClassBased ? tNode.residualClasses : tNode.residualStyles;
if (residual != null /** OR residual !=== undefined */) {
value = arrayMapGet(residual !, prop);
value = keyValueArrayGet(residual !, prop);
}
}
return value;
@ -776,38 +806,6 @@ function isStylingValuePresent(value: any): boolean {
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.
*
@ -818,7 +816,7 @@ export function initializeStylingStaticArrayMap(tNode: TNode) {
function normalizeAndApplySuffixOrSanitizer(
value: any, suffixOrSanitizer: SanitizerFn | string | undefined | null): string|null|undefined|
boolean {
if (value === null || value === undefined) {
if (value == null /** || value === undefined */) {
// do nothing
} else if (typeof suffixOrSanitizer === 'function') {
// sanitize the value.

View File

@ -5,7 +5,7 @@
* Use of this source code is governed by an MIT-style license that can be
* found in the LICENSE file at https://angular.io/license
*/
import {ArrayMap} from '../../util/array_utils';
import {KeyValueArray} from '../../util/array_utils';
import {TStylingRange} from '../interfaces/styling';
import {DirectiveDef} from './definition';
@ -498,7 +498,7 @@ export interface TNode {
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
* 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.
* - `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.
@ -536,15 +536,15 @@ export interface TNode {
classes: string|null;
/**
* An `ArrayMap` version of residual `classes`.
* A `KeyValueArray` version of residual `classes`.
*
* Same as `TNode.residualStyles` but for classes.
*
* - `undefined': not initialized.
* - `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.

View File

@ -6,7 +6,7 @@
* 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';
/**
@ -32,7 +32,7 @@ export type TStylingKeyPrimitive = string | null | false;
/**
* 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
* `""` contains the wrapped value.
*
@ -89,7 +89,7 @@ export type TStylingKeyPrimitive = string | null | false;
*
* 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.

View File

@ -915,14 +915,15 @@ function applyContainer(
* @param isClassBased `true` if it should be written to `class` (`false` to write to `style`)
* @param rNode The Node to write to.
* @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
* otherwise).
* @param value Value to write. If `null`/`undefined`/`false` this is considered a remove (set/add
* otherwise).
*/
export function applyStyling(
renderer: Renderer3, isClassBased: boolean, rNode: RElement, prop: string, value: any) {
const isProcedural = isProceduralRenderer(renderer);
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++;
if (isProcedural) {
(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
// different order which causes failures. Using direct constant as workaround for now.
const flags = prop.indexOf('-') == -1 ? undefined : 2 /* RendererStyleFlags2.DashCase */;
if (value === null || value === undefined) {
if (value == null /** || value === undefined */) {
ngDevMode && ngDevMode.rendererRemoveStyle++;
if (isProcedural) {
(renderer as Renderer2).removeStyle(rNode, prop, flags);

View File

@ -240,21 +240,6 @@ export function getLView(): 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.
*
@ -562,11 +547,3 @@ export function getCurrentStyleSanitizer() {
const lFrame = instructionState.lFrame;
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
*/
import {ArrayMap, arrayMapIndexOf} from '../../util/array_utils';
import {KeyValueArray, keyValueArrayIndexOf} from '../../util/array_utils';
import {assertDataInRange, assertEqual, assertNotEqual} from '../../util/assert';
import {assertFirstUpdatePass} from '../assert';
import {TNode} from '../interfaces/node';
@ -204,10 +204,11 @@ export function insertTStylingBinding(
let tStylingKey: TStylingKeyPrimitive;
if (Array.isArray(tStylingKeyWithStatic)) {
// We are case when the `TStylingKey` contains static fields as well.
const staticArrayMap = tStylingKeyWithStatic as ArrayMap<any>;
tStylingKey = staticArrayMap[1]; // unwrap.
const staticKeyValueArray = tStylingKeyWithStatic as KeyValueArray<any>;
tStylingKey = staticKeyValueArray[1]; // unwrap.
// 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.
isKeyDuplicateOfStatic = true;
}
@ -292,7 +293,7 @@ function markDuplicateOfResidualStyling(
tNode: TNode, tStylingKey: TStylingKey, tData: TData, index: number, isClassBinding: boolean) {
const residual = isClassBinding ? tNode.residualClasses : tNode.residualStyles;
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.
tData[index + 1] = setTStylingRangeNextDuplicate(tData[index + 1] as TStylingRange);
}
@ -418,9 +419,10 @@ function isStylingMatch(tStylingKeyCursor: TStylingKey, tStylingKey: TStylingKey
) {
return true;
} 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.
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;
}