perf(ivy): speed up bindings when no directives are present (#32919)
Prior to this fix, whenever a style or class binding is present, the binding application process would require an instance of `TStylingContext` to be built regardless of whether or not any binding resolution is needed (just so that it knows whether or not there are any collisions). This check is, however, unnecessary because if (and only if) there are directives present on the element then are collisions possible. This patch removes the need for style/class bindings to register themselves on to a `TStylingContext` if there are no directives and present on an element. This means that all map and prop-based style/class bindings are applied as soon as bindings are updated on an element. PR Close #32919
This commit is contained in:

committed by
Miško Hevery

parent
8d111da7f6
commit
b2decf0266
@ -10,7 +10,7 @@ import {StyleSanitizeFn, StyleSanitizeMode} from '../../sanitization/style_sanit
|
||||
import {ProceduralRenderer3, RElement, Renderer3, RendererStyleFlags3, isProceduralRenderer} from '../interfaces/renderer';
|
||||
import {ApplyStylingFn, LStylingData, StylingMapArray, StylingMapArrayIndex, StylingMapsSyncMode, SyncStylingMapsFn, TStylingConfig, TStylingContext, TStylingContextIndex, TStylingContextPropConfigFlags} from '../interfaces/styling';
|
||||
import {NO_CHANGE} from '../tokens';
|
||||
import {DEFAULT_BINDING_INDEX, DEFAULT_BINDING_VALUE, DEFAULT_GUARD_MASK_VALUE, MAP_BASED_ENTRY_PROP_NAME, getBindingValue, getConfig, getDefaultValue, getGuardMask, getMapProp, getMapValue, getProp, getPropValuesStartPosition, getStylingMapArray, getTotalSources, getValue, getValuesCount, hasConfig, hasValueChanged, isContextLocked, isHostStylingActive, isSanitizationRequired, isStylingValueDefined, lockContext, patchConfig, setDefaultValue, setGuardMask, setMapAsDirty, setValue} from '../util/styling_utils';
|
||||
import {DEFAULT_BINDING_INDEX, DEFAULT_BINDING_VALUE, DEFAULT_GUARD_MASK_VALUE, MAP_BASED_ENTRY_PROP_NAME, TEMPLATE_DIRECTIVE_INDEX, getBindingValue, getConfig, getDefaultValue, getGuardMask, getInitialStylingValue, getMapProp, getMapValue, getProp, getPropValuesStartPosition, getStylingMapArray, getTotalSources, getValue, getValuesCount, hasConfig, hasValueChanged, isContextLocked, isHostStylingActive, isSanitizationRequired, isStylingValueDefined, lockContext, patchConfig, setDefaultValue, setGuardMask, setMapAsDirty, setValue} from '../util/styling_utils';
|
||||
|
||||
import {getStylingState, resetStylingState} from './state';
|
||||
|
||||
@ -143,7 +143,6 @@ function updateBindingData(
|
||||
patchConfig(
|
||||
context,
|
||||
hostBindingsMode ? TStylingConfig.HasHostBindings : TStylingConfig.HasTemplateBindings);
|
||||
patchConfig(context, prop ? TStylingConfig.HasPropBindings : TStylingConfig.HasMapBindings);
|
||||
}
|
||||
|
||||
const changed = forceUpdate || hasValueChanged(data[bindingIndex], value);
|
||||
@ -629,22 +628,58 @@ export function applyStylingViaContext(
|
||||
* automatically. This function is intended to be used for performance reasons in the
|
||||
* event that there is no need to apply styling via context resolution.
|
||||
*
|
||||
* See `allowDirectStylingApply`.
|
||||
* This function has three different cases that can occur (for each item in the map):
|
||||
*
|
||||
* - Case 1: Attempt to apply the current value in the map to the element (if it's `non null`).
|
||||
*
|
||||
* - Case 2: If a map value fails to be applied then the algorithm will find a matching entry in
|
||||
* the initial values present in the context and attempt to apply that.
|
||||
*
|
||||
* - Default Case: If the initial value cannot be applied then a default value of `null` will be
|
||||
* applied (which will remove the style/class value from the element).
|
||||
*
|
||||
* See `allowDirectStylingApply` to learn the logic used to determine whether any style/class
|
||||
* bindings can be directly applied.
|
||||
*
|
||||
* @returns whether or not the styling map was applied to the element.
|
||||
*/
|
||||
export function applyStylingMapDirectly(
|
||||
renderer: any, context: TStylingContext, element: RElement, data: LStylingData,
|
||||
bindingIndex: number, map: StylingMapArray, applyFn: ApplyStylingFn,
|
||||
bindingIndex: number, map: StylingMapArray, isClassBased: boolean, applyFn: ApplyStylingFn,
|
||||
sanitizer?: StyleSanitizeFn | null, forceUpdate?: boolean): boolean {
|
||||
if (forceUpdate || hasValueChanged(data[bindingIndex], map)) {
|
||||
setValue(data, bindingIndex, map);
|
||||
const initialStyles =
|
||||
hasConfig(context, TStylingConfig.HasInitialStyling) ? getStylingMapArray(context) : null;
|
||||
|
||||
for (let i = StylingMapArrayIndex.ValuesStartPosition; i < map.length;
|
||||
i += StylingMapArrayIndex.TupleSize) {
|
||||
const prop = getMapProp(map, i);
|
||||
const value = getMapValue(map, i);
|
||||
applyStylingValue(renderer, context, element, prop, value, applyFn, bindingIndex, sanitizer);
|
||||
|
||||
// case 1: apply the map value (if it exists)
|
||||
let applied =
|
||||
applyStylingValue(renderer, element, prop, value, applyFn, bindingIndex, sanitizer);
|
||||
|
||||
// case 2: apply the initial value (if it exists)
|
||||
if (!applied && initialStyles) {
|
||||
applied = findAndApplyMapValue(
|
||||
renderer, element, applyFn, initialStyles, prop, bindingIndex, sanitizer);
|
||||
}
|
||||
|
||||
// default case: apply `null` to remove the value
|
||||
if (!applied) {
|
||||
applyFn(renderer, element, prop, null, bindingIndex);
|
||||
}
|
||||
}
|
||||
|
||||
const state = getStylingState(element, TEMPLATE_DIRECTIVE_INDEX);
|
||||
if (isClassBased) {
|
||||
state.lastDirectClassMap = map;
|
||||
} else {
|
||||
state.lastDirectStyleMap = map;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
@ -657,47 +692,95 @@ export function applyStylingMapDirectly(
|
||||
* automatically. This function is intended to be used for performance reasons in the
|
||||
* event that there is no need to apply styling via context resolution.
|
||||
*
|
||||
* See `allowDirectStylingApply`.
|
||||
* This function has four different cases that can occur:
|
||||
*
|
||||
* - Case 1: Apply the provided prop/value (style or class) entry to the element
|
||||
* (if it is `non null`).
|
||||
*
|
||||
* - Case 2: If value does not get applied (because its `null` or `undefined`) then the algorithm
|
||||
* will check to see if a styling map value was applied to the element as well just
|
||||
* before this (via `styleMap` or `classMap`). If and when a map is present then the
|
||||
* algorithm will find the matching property in the map and apply its value.
|
||||
*
|
||||
* - Case 3: If a map value fails to be applied then the algorithm will check to see if there
|
||||
* are any initial values present and attempt to apply a matching value based on
|
||||
* the target prop.
|
||||
*
|
||||
* - Default Case: If a matching initial value cannot be applied then a default value
|
||||
* of `null` will be applied (which will remove the style/class value
|
||||
* from the element).
|
||||
*
|
||||
* See `allowDirectStylingApply` to learn the logic used to determine whether any style/class
|
||||
* bindings can be directly applied.
|
||||
*
|
||||
* @returns whether or not the prop/value styling was applied to the element.
|
||||
*/
|
||||
export function applyStylingValueDirectly(
|
||||
renderer: any, context: TStylingContext, element: RElement, data: LStylingData,
|
||||
bindingIndex: number, prop: string, value: any, applyFn: ApplyStylingFn,
|
||||
bindingIndex: number, prop: string, value: any, isClassBased: boolean, applyFn: ApplyStylingFn,
|
||||
sanitizer?: StyleSanitizeFn | null): boolean {
|
||||
let applied = false;
|
||||
if (hasValueChanged(data[bindingIndex], value)) {
|
||||
setValue(data, bindingIndex, value);
|
||||
applyStylingValue(renderer, context, element, prop, value, applyFn, bindingIndex, sanitizer);
|
||||
|
||||
// case 1: apply the provided value (if it exists)
|
||||
applied = applyStylingValue(renderer, element, prop, value, applyFn, bindingIndex, sanitizer);
|
||||
|
||||
// case 2: find the matching property in a styling map and apply the detected value
|
||||
if (!applied && hasConfig(context, TStylingConfig.HasMapBindings)) {
|
||||
const state = getStylingState(element, TEMPLATE_DIRECTIVE_INDEX);
|
||||
const map = isClassBased ? state.lastDirectClassMap : state.lastDirectStyleMap;
|
||||
applied = map ?
|
||||
findAndApplyMapValue(renderer, element, applyFn, map, prop, bindingIndex, sanitizer) :
|
||||
false;
|
||||
}
|
||||
|
||||
// case 3: apply the initial value (if it exists)
|
||||
if (!applied && hasConfig(context, TStylingConfig.HasInitialStyling)) {
|
||||
const map = getStylingMapArray(context);
|
||||
applied =
|
||||
map ? findAndApplyMapValue(renderer, element, applyFn, map, prop, bindingIndex) : false;
|
||||
}
|
||||
|
||||
// default case: apply `null` to remove the value
|
||||
if (!applied) {
|
||||
applyFn(renderer, element, prop, null, bindingIndex);
|
||||
}
|
||||
}
|
||||
return applied;
|
||||
}
|
||||
|
||||
function applyStylingValue(
|
||||
renderer: any, element: RElement, prop: string, value: any, applyFn: ApplyStylingFn,
|
||||
bindingIndex: number, sanitizer?: StyleSanitizeFn | null): boolean {
|
||||
let valueToApply: string|null = unwrapSafeValue(value);
|
||||
if (isStylingValueDefined(valueToApply)) {
|
||||
valueToApply =
|
||||
sanitizer ? sanitizer(prop, value, StyleSanitizeMode.SanitizeOnly) : valueToApply;
|
||||
applyFn(renderer, element, prop, valueToApply, bindingIndex);
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
function applyStylingValue(
|
||||
renderer: any, context: TStylingContext, element: RElement, prop: string, value: any,
|
||||
applyFn: ApplyStylingFn, bindingIndex: number, sanitizer?: StyleSanitizeFn | null) {
|
||||
let valueToApply: string|null = unwrapSafeValue(value);
|
||||
if (isStylingValueDefined(valueToApply)) {
|
||||
valueToApply =
|
||||
sanitizer ? sanitizer(prop, value, StyleSanitizeMode.SanitizeOnly) : valueToApply;
|
||||
} else if (hasConfig(context, TStylingConfig.HasInitialStyling)) {
|
||||
const initialStyles = getStylingMapArray(context);
|
||||
if (initialStyles) {
|
||||
valueToApply = findInitialStylingValue(initialStyles, prop);
|
||||
}
|
||||
}
|
||||
applyFn(renderer, element, prop, valueToApply, bindingIndex);
|
||||
}
|
||||
|
||||
function findInitialStylingValue(map: StylingMapArray, prop: string): string|null {
|
||||
function findAndApplyMapValue(
|
||||
renderer: any, element: RElement, applyFn: ApplyStylingFn, map: StylingMapArray, prop: string,
|
||||
bindingIndex: number, sanitizer?: StyleSanitizeFn | null) {
|
||||
for (let i = StylingMapArrayIndex.ValuesStartPosition; i < map.length;
|
||||
i += StylingMapArrayIndex.TupleSize) {
|
||||
const p = getMapProp(map, i);
|
||||
if (p >= prop) {
|
||||
return p === prop ? getMapValue(map, i) : null;
|
||||
if (p === prop) {
|
||||
let valueToApply = getMapValue(map, i);
|
||||
valueToApply =
|
||||
sanitizer ? sanitizer(prop, valueToApply, StyleSanitizeMode.SanitizeOnly) : valueToApply;
|
||||
applyFn(renderer, element, prop, valueToApply, bindingIndex);
|
||||
return true;
|
||||
}
|
||||
if (p > prop) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
return false;
|
||||
}
|
||||
|
||||
function normalizeBitMaskValue(value: number | boolean): number {
|
||||
|
@ -6,6 +6,7 @@
|
||||
* found in the LICENSE file at https://angular.io/license
|
||||
*/
|
||||
import {RElement} from '../interfaces/renderer';
|
||||
import {StylingMapArray} from '../interfaces/styling';
|
||||
import {TEMPLATE_DIRECTIVE_INDEX} from '../util/styling_utils';
|
||||
|
||||
/**
|
||||
@ -57,6 +58,26 @@ export interface StylingState {
|
||||
|
||||
/** The styles update bit index value that is processed during each style binding */
|
||||
stylesIndex: number;
|
||||
|
||||
/**
|
||||
* The last class map that was applied (i.e. `[class]="x"`).
|
||||
*
|
||||
* Note that this property is only populated when direct class values are applied
|
||||
* (i.e. context resolution is not used).
|
||||
*
|
||||
* See `allowDirectStyling` for more info.
|
||||
*/
|
||||
lastDirectClassMap: StylingMapArray|null;
|
||||
|
||||
/**
|
||||
* The last style map that was applied (i.e. `[style]="x"`)
|
||||
*
|
||||
* Note that this property is only populated when direct style values are applied
|
||||
* (i.e. context resolution is not used).
|
||||
*
|
||||
* See `allowDirectStyling` for more info.
|
||||
*/
|
||||
lastDirectStyleMap: StylingMapArray|null;
|
||||
}
|
||||
|
||||
// these values will get filled in the very first time this is accessed...
|
||||
@ -68,6 +89,8 @@ const _state: StylingState = {
|
||||
classesIndex: -1,
|
||||
stylesBitMask: -1,
|
||||
stylesIndex: -1,
|
||||
lastDirectClassMap: null,
|
||||
lastDirectStyleMap: null,
|
||||
};
|
||||
|
||||
const BIT_MASK_START_VALUE = 0;
|
||||
@ -99,6 +122,8 @@ export function getStylingState(element: RElement, directiveIndex: number): Styl
|
||||
_state.classesIndex = INDEX_START_VALUE;
|
||||
_state.stylesBitMask = BIT_MASK_START_VALUE;
|
||||
_state.stylesIndex = INDEX_START_VALUE;
|
||||
_state.lastDirectClassMap = null;
|
||||
_state.lastDirectStyleMap = null;
|
||||
} else if (_state.directiveIndex !== directiveIndex) {
|
||||
_state.directiveIndex = directiveIndex;
|
||||
_state.sourceIndex++;
|
||||
|
Reference in New Issue
Block a user