fix(ivy): ensure static styling is properly inherited into child components (#29015)

Angular supports having a component extend off of a parent component.
When this happens, all annotation-level data is inherited including styles
and classes. Up until now, Ivy only paid attention to static styling
values on the parent component and not the child component. This patch
ensures that both the parent's component and child component's styling
data is merged and rendered accordingly.

Jira Issue: FW-1081

PR Close #29015
This commit is contained in:
Matias Niemelä
2019-03-01 14:15:11 -08:00
committed by Andrew Kushnir
parent 48214e2a05
commit 78adcfe0ee
13 changed files with 721 additions and 529 deletions

View File

@ -30,9 +30,9 @@ import {ComponentDef, ComponentTemplate, DirectiveDef, DirectiveDefListOrFactory
import {INJECTOR_BLOOM_PARENT_SIZE, NodeInjectorFactory} from './interfaces/injector';
import {AttributeMarker, InitialInputData, InitialInputs, LocalRefExtractor, PropertyAliasValue, PropertyAliases, TAttributes, TContainerNode, TElementContainerNode, TElementNode, TIcuContainerNode, TNode, TNodeFlags, TNodeProviderIndexes, TNodeType, TProjectionNode, TViewNode} from './interfaces/node';
import {PlayerFactory} from './interfaces/player';
import {CssSelectorList, NG_PROJECT_AS_ATTR_NAME} from './interfaces/projection';
import {CssSelectorList} from './interfaces/projection';
import {LQueries} from './interfaces/query';
import {GlobalTargetResolver, ProceduralRenderer3, RComment, RElement, RText, Renderer3, RendererFactory3, isProceduralRenderer} from './interfaces/renderer';
import {GlobalTargetResolver, RComment, RElement, RText, Renderer3, RendererFactory3, isProceduralRenderer} from './interfaces/renderer';
import {SanitizerFn} from './interfaces/sanitization';
import {StylingContext} from './interfaces/styling';
import {BINDING_INDEX, CHILD_HEAD, CHILD_TAIL, CLEANUP, CONTEXT, DECLARATION_VIEW, ExpandoInstructions, FLAGS, HEADER_OFFSET, HOST, INJECTOR, InitPhaseState, LView, LViewFlags, NEXT, OpaqueViewState, PARENT, QUERIES, RENDERER, RENDERER_FACTORY, RootContext, RootContextFlags, SANITIZER, TData, TVIEW, TView, T_HOST} from './interfaces/view';
@ -43,8 +43,9 @@ import {applyOnCreateInstructions} from './node_util';
import {decreaseElementDepthCount, enterView, getBindingsEnabled, getCheckNoChangesMode, getContextLView, getCurrentDirectiveDef, getElementDepthCount, getIsParent, getLView, getPreviousOrParentTNode, increaseElementDepthCount, isCreationMode, leaveView, nextContextImpl, resetComponentState, setBindingRoot, setCheckNoChangesMode, setCurrentDirectiveDef, setCurrentQueryIndex, setIsParent, setPreviousOrParentTNode} from './state';
import {getInitialClassNameValue, getInitialStyleStringValue, initializeStaticContext as initializeStaticStylingContext, patchContextWithStaticAttrs, renderInitialClasses, renderInitialStyles, renderStyling, updateClassProp as updateElementClassProp, updateContextWithBindings, updateStyleProp as updateElementStyleProp, updateStylingMap} from './styling/class_and_style_bindings';
import {BoundPlayerFactory} from './styling/player_factory';
import {ANIMATION_PROP_PREFIX, allocateDirectiveIntoContext, createEmptyStylingContext, forceClassesAsString, forceStylesAsString, getStylingContext, hasClassInput, hasStyleInput, hasStyling, isAnimationProp} from './styling/util';
import {ANIMATION_PROP_PREFIX, allocateDirectiveIntoContext, createEmptyStylingContext, forceClassesAsString, forceStylesAsString, getStylingContext, hasClassInput, hasStyleInput, isAnimationProp} from './styling/util';
import {NO_CHANGE} from './tokens';
import {attrsStylingIndexOf, setUpAttributes} from './util/attrs_utils';
import {INTERPOLATION_DELIMITER, renderStringify} from './util/misc_utils';
import {findComponentView, getLViewParent, getRootContext, getRootView} from './util/view_traversal_utils';
import {getComponentViewByIndex, getNativeByIndex, getNativeByTNode, getTNode, isComponent, isComponentDef, isContentQueryHost, isRootView, loadInternal, readPatchedLView, unwrapRNode, viewAttachedToChangeDetector} from './util/view_utils';
@ -620,15 +621,21 @@ export function elementStart(
const tNode = createNodeAtIndex(index, TNodeType.Element, native !, name, attrs || null);
if (attrs) {
const lastAttrIndex = setUpAttributes(native, attrs);
// it's important to only prepare styling-related datastructures once for a given
// tNode and not each time an element is created. Also, the styling code is designed
// to be patched and constructed at various points, but only up until the first element
// is created. Then the styling context is locked and can only be instantiated for each
// successive element that is created.
if (tView.firstTemplatePass && !tNode.stylingTemplate && hasStyling(attrs)) {
tNode.stylingTemplate = initializeStaticStylingContext(attrs);
// to be patched and constructed at various points, but only up until the styling
// template is first allocated (which happens when the very first style/class binding
// value is evaluated). When the template is allocated (when it turns into a context)
// then the styling template is locked and cannot be further extended (it can only be
// instantiated into a context per element)
if (tView.firstTemplatePass && !tNode.stylingTemplate) {
const stylingAttrsStartIndex = attrsStylingIndexOf(attrs, lastAttrIndex);
if (stylingAttrsStartIndex >= 0) {
tNode.stylingTemplate = initializeStaticStylingContext(attrs, stylingAttrsStartIndex);
}
}
setUpAttributes(native, attrs);
}
appendChild(native, tNode, lView);
@ -825,87 +832,6 @@ function createViewBlueprint(bindingStartIndex: number, initialViewLength: numbe
return blueprint;
}
/**
* Assigns all attribute values to the provided element via the inferred renderer.
*
* This function accepts two forms of attribute entries:
*
* default: (key, value):
* attrs = [key1, value1, key2, value2]
*
* namespaced: (NAMESPACE_MARKER, uri, name, value)
* attrs = [NAMESPACE_MARKER, uri, name, value, NAMESPACE_MARKER, uri, name, value]
*
* The `attrs` array can contain a mix of both the default and namespaced entries.
* The "default" values are set without a marker, but if the function comes across
* a marker value then it will attempt to set a namespaced value. If the marker is
* not of a namespaced value then the function will quit and return the index value
* where it stopped during the iteration of the attrs array.
*
* See [AttributeMarker] to understand what the namespace marker value is.
*
* Note that this instruction does not support assigning style and class values to
* an element. See `elementStart` and `elementHostAttrs` to learn how styling values
* are applied to an element.
*
* @param native The element that the attributes will be assigned to
* @param attrs The attribute array of values that will be assigned to the element
* @returns the index value that was last accessed in the attributes array
*/
function setUpAttributes(native: RElement, attrs: TAttributes): number {
const renderer = getLView()[RENDERER];
const isProc = isProceduralRenderer(renderer);
let i = 0;
while (i < attrs.length) {
const value = attrs[i];
if (typeof value === 'number') {
// only namespaces are supported. Other value types (such as style/class
// entries) are not supported in this function.
if (value !== AttributeMarker.NamespaceURI) {
break;
}
// we just landed on the marker value ... therefore
// we should skip to the next entry
i++;
const namespaceURI = attrs[i++] as string;
const attrName = attrs[i++] as string;
const attrVal = attrs[i++] as string;
ngDevMode && ngDevMode.rendererSetAttribute++;
isProc ?
(renderer as ProceduralRenderer3).setAttribute(native, attrName, attrVal, namespaceURI) :
native.setAttributeNS(namespaceURI, attrName, attrVal);
} else {
/// attrName is string;
const attrName = value as string;
const attrVal = attrs[++i];
if (attrName !== NG_PROJECT_AS_ATTR_NAME) {
// Standard attributes
ngDevMode && ngDevMode.rendererSetAttribute++;
if (isAnimationProp(attrName)) {
if (isProc) {
(renderer as ProceduralRenderer3).setProperty(native, attrName, attrVal);
}
} else {
isProc ?
(renderer as ProceduralRenderer3)
.setAttribute(native, attrName as string, attrVal as string) :
native.setAttribute(attrName as string, attrVal as string);
}
}
i++;
}
}
// another piece of code may iterate over the same attributes array. Therefore
// it may be helpful to return the exact spot where the attributes array exited
// whether by running into an unsupported marker or if all the static values were
// iterated over.
return i;
}
export function createError(text: string, token: any) {
return new Error(`Renderer: ${text} [${renderStringify(token)}]`);
}
@ -1556,13 +1482,18 @@ function initElementStyling(
*/
export function elementHostAttrs(directive: any, attrs: TAttributes) {
const tNode = getPreviousOrParentTNode();
if (!tNode.stylingTemplate) {
tNode.stylingTemplate = initializeStaticStylingContext(attrs);
}
const lView = getLView();
const native = getNativeByTNode(tNode, lView) as RElement;
const i = setUpAttributes(native, attrs);
patchContextWithStaticAttrs(tNode.stylingTemplate, attrs, i, directive);
const lastAttrIndex = setUpAttributes(native, attrs);
const stylingAttrsStartIndex = attrsStylingIndexOf(attrs, lastAttrIndex);
if (stylingAttrsStartIndex >= 0) {
if (tNode.stylingTemplate) {
patchContextWithStaticAttrs(tNode.stylingTemplate, attrs, stylingAttrsStartIndex, directive);
} else {
tNode.stylingTemplate =
initializeStaticStylingContext(attrs, stylingAttrsStartIndex, directive);
}
}
}
/**

View File

@ -323,9 +323,9 @@ export interface StylingContext extends
*
* See [InitialStylingValuesIndex] for a breakdown of how all this works.
*/
export interface InitialStylingValues extends Array<string|boolean|null> {
export interface InitialStylingValues extends Array<string|boolean|number|null> {
[InitialStylingValuesIndex.DefaultNullValuePosition]: null;
[InitialStylingValuesIndex.InitialClassesStringPosition]: string|null;
[InitialStylingValuesIndex.CachedStringValuePosition]: string|null;
}
/**
@ -432,12 +432,48 @@ export interface InitialStylingValues extends Array<string|boolean|null> {
* ```
*/
export const enum InitialStylingValuesIndex {
/**
* The first value is always `null` so that `styles[0] == null` for unassigned values
*/
DefaultNullValuePosition = 0,
InitialClassesStringPosition = 1,
/**
* Used for non-styling code to examine what the style or className string is:
* styles: ['width', '100px', 0, 'opacity', null, 0, 'height', '200px', 0]
* => initialStyles[CachedStringValuePosition] = 'width:100px;height:200px';
* classes: ['foo', true, 0, 'bar', false, 0, 'baz', true, 0]
* => initialClasses[CachedStringValuePosition] = 'foo bar';
*
* Note that this value is `null` by default and it will only be populated
* once `getInitialStyleStringValue` or `getInitialClassNameValue` is executed.
*/
CachedStringValuePosition = 1,
/**
* Where the style or class values start in the tuple
*/
KeyValueStartPosition = 2,
/**
* The offset value (index + offset) for the property value for each style/class entry
*/
PropOffset = 0,
/**
* The offset value (index + offset) for the style/class value for each style/class entry
*/
ValueOffset = 1,
Size = 2
/**
* The offset value (index + offset) for the style/class directive owner for each style/class
entry
*/
DirectiveOwnerOffset = 2,
/**
* The total size for each style/class entry (prop + value + directiveOwner)
*/
Size = 3
}
/**

View File

@ -41,29 +41,10 @@ import {addPlayerInternal, allocPlayerContext, allocateDirectiveIntoContext, cre
/**
* Creates a new StylingContext an fills it with the provided static styling attribute values.
*/
export function initializeStaticContext(attrs: TAttributes): StylingContext {
export function initializeStaticContext(
attrs: TAttributes, stylingStartIndex: number, directiveRef?: any | null): StylingContext {
const context = createEmptyStylingContext();
const initialClasses: InitialStylingValues = context[StylingIndex.InitialClassValuesPosition] =
[null, null];
const initialStyles: InitialStylingValues = context[StylingIndex.InitialStyleValuesPosition] =
[null, null];
// The attributes array has marker values (numbers) indicating what the subsequent
// values represent. When we encounter a number, we set the mode to that type of attribute.
let mode = -1;
for (let i = 0; i < attrs.length; i++) {
const attr = attrs[i];
if (typeof attr == 'number') {
mode = attr;
} else if (mode === AttributeMarker.Styles) {
initialStyles.push(attr as string, attrs[++i] as string);
} else if (mode === AttributeMarker.Classes) {
initialClasses.push(attr as string, true);
} else if (mode === AttributeMarker.SelectOnly) {
break;
}
}
patchContextWithStaticAttrs(context, attrs, stylingStartIndex, directiveRef);
return context;
}
@ -74,34 +55,41 @@ export function initializeStaticContext(attrs: TAttributes): StylingContext {
* @param context the existing styling context
* @param attrs an array of new static styling attributes that will be
* assigned to the context
* @param attrsStylingStartIndex what index to start iterating within the
* provided `attrs` array to start reading style and class values
* @param directiveRef the directive instance with which static data is associated with.
*/
export function patchContextWithStaticAttrs(
context: StylingContext, attrs: TAttributes, startingIndex: number, directiveRef: any): void {
context: StylingContext, attrs: TAttributes, attrsStylingStartIndex: number,
directiveRef?: any | null): void {
// this means the context has already been set and instantiated
if (context[StylingIndex.MasterFlagPosition] & StylingFlags.BindingAllocationLocked) return;
// If the styling context has already been patched with the given directive's bindings,
// then there is no point in doing it again. The reason why this may happen (the directive
// styling being patched twice) is because the `stylingBinding` function is called each time
// an element is created (both within a template function and within directive host bindings).
const directives = context[StylingIndex.DirectiveRegistryPosition];
if (getDirectiveRegistryValuesIndexOf(directives, directiveRef) == -1) {
let detectedIndex = getDirectiveRegistryValuesIndexOf(directives, directiveRef || null);
if (detectedIndex === -1) {
// this is a new directive which we have not seen yet.
allocateDirectiveIntoContext(context, directiveRef);
detectedIndex = allocateDirectiveIntoContext(context, directiveRef);
}
const directiveIndex = detectedIndex / DirectiveRegistryValuesIndex.Size;
let initialClasses: InitialStylingValues|null = null;
let initialStyles: InitialStylingValues|null = null;
let mode = -1;
for (let i = startingIndex; i < attrs.length; i++) {
const attr = attrs[i];
if (typeof attr == 'number') {
mode = attr;
} else if (mode == AttributeMarker.Classes) {
initialClasses = initialClasses || context[StylingIndex.InitialClassValuesPosition];
patchInitialStylingValue(initialClasses, attr, true);
} else if (mode == AttributeMarker.Styles) {
initialStyles = initialStyles || context[StylingIndex.InitialStyleValuesPosition];
patchInitialStylingValue(initialStyles, attr, attrs[++i]);
}
let initialClasses: InitialStylingValues|null = null;
let initialStyles: InitialStylingValues|null = null;
let mode = -1;
for (let i = attrsStylingStartIndex; i < attrs.length; i++) {
const attr = attrs[i];
if (typeof attr == 'number') {
mode = attr;
} else if (mode == AttributeMarker.Classes) {
initialClasses = initialClasses || context[StylingIndex.InitialClassValuesPosition];
patchInitialStylingValue(initialClasses, attr, true, directiveIndex);
} else if (mode == AttributeMarker.Styles) {
initialStyles = initialStyles || context[StylingIndex.InitialStyleValuesPosition];
patchInitialStylingValue(initialStyles, attr, attrs[++i], directiveIndex);
}
}
}
@ -110,29 +98,44 @@ export function patchContextWithStaticAttrs(
* Designed to add a style or class value into the existing set of initial styles.
*
* The function will search and figure out if a style/class value is already present
* within the provided initial styling array. If and when a style/class value is not
* present (or if it's value is falsy) then it will be inserted/updated in the list
* of initial styling values.
* within the provided initial styling array. If and when a style/class value is
* present (allocated) then the code below will set the new value depending on the
* following cases:
*
* 1) if the existing value is falsy (this happens because a `[class.prop]` or
* `[style.prop]` binding was set, but there wasn't a matching static style
* or class present on the context)
* 2) if the value was set already by the template, component or directive, but the
* new value is set on a higher level (i.e. a sub component which extends a parent
* component sets its value after the parent has already set the same one)
* 3) if the same directive provides a new set of styling values to set
*
* @param initialStyling the initial styling array where the new styling entry will be added to
* @param prop the property value of the new entry (e.g. `width` (styles) or `foo` (classes))
* @param value the styling value of the new entry (e.g. `absolute` (styles) or `true` (classes))
* @param directiveOwnerIndex the directive owner index value of the styling source responsible
* for these styles (see `interfaces/styling.ts#directives` for more info)
*/
function patchInitialStylingValue(
initialStyling: InitialStylingValues, prop: string, value: any): void {
// Even values are keys; Odd numbers are values; Search keys only
for (let i = InitialStylingValuesIndex.KeyValueStartPosition; i < initialStyling.length;) {
const key = initialStyling[i];
initialStyling: InitialStylingValues, prop: string, value: any,
directiveOwnerIndex: number): void {
for (let i = InitialStylingValuesIndex.KeyValueStartPosition; i < initialStyling.length;
i += InitialStylingValuesIndex.Size) {
const key = initialStyling[i + InitialStylingValuesIndex.PropOffset];
if (key === prop) {
const existingValue = initialStyling[i + InitialStylingValuesIndex.ValueOffset];
// If there is no previous style value (when `null`) or no previous class
// applied (when `false`) then we update the the newly given value.
if (existingValue == null || existingValue == false) {
initialStyling[i + InitialStylingValuesIndex.ValueOffset] = value;
const existingValue =
initialStyling[i + InitialStylingValuesIndex.ValueOffset] as string | null | boolean;
const existingOwner =
initialStyling[i + InitialStylingValuesIndex.DirectiveOwnerOffset] as number;
if (allowValueChange(existingValue, value, existingOwner, directiveOwnerIndex)) {
addOrUpdateStaticStyle(i, initialStyling, prop, value, directiveOwnerIndex);
}
return;
}
i = i + InitialStylingValuesIndex.Size;
}
// We did not find existing key, add a new one.
initialStyling.push(prop, value);
addOrUpdateStaticStyle(null, initialStyling, prop, value, directiveOwnerIndex);
}
/**
@ -377,8 +380,10 @@ export function updateContextWithBindings(
let initialValuesToLookup = entryIsClassBased ? initialClasses : initialStyles;
let indexForInitial = getInitialStylingValuesIndexOf(initialValuesToLookup, propName);
if (indexForInitial === -1) {
indexForInitial = initialValuesToLookup.length + InitialStylingValuesIndex.ValueOffset;
initialValuesToLookup.push(propName, entryIsClassBased ? false : null);
indexForInitial = addOrUpdateStaticStyle(
null, initialValuesToLookup, propName, entryIsClassBased ? false : null,
directiveIndex) +
InitialStylingValuesIndex.ValueOffset;
} else {
indexForInitial += InitialStylingValuesIndex.ValueOffset;
}
@ -1262,7 +1267,7 @@ function getInitialValue(context: StylingContext, flag: number): string|boolean|
const entryIsClassBased = flag & StylingFlags.Class;
const initialValues = entryIsClassBased ? context[StylingIndex.InitialClassValuesPosition] :
context[StylingIndex.InitialStyleValuesPosition];
return initialValues[index];
return initialValues[index] as string | boolean | null;
}
function getInitialIndex(flag: number): number {
@ -1769,7 +1774,7 @@ function allowValueChange(
*/
export function getInitialClassNameValue(context: StylingContext): string {
const initialClassValues = context[StylingIndex.InitialClassValuesPosition];
let className = initialClassValues[InitialStylingValuesIndex.InitialClassesStringPosition];
let className = initialClassValues[InitialStylingValuesIndex.CachedStringValuePosition];
if (className === null) {
className = '';
for (let i = InitialStylingValuesIndex.KeyValueStartPosition; i < initialClassValues.length;
@ -1779,7 +1784,7 @@ export function getInitialClassNameValue(context: StylingContext): string {
className += (className.length ? ' ' : '') + initialClassValues[i];
}
}
initialClassValues[InitialStylingValuesIndex.InitialClassesStringPosition] = className;
initialClassValues[InitialStylingValuesIndex.CachedStringValuePosition] = className;
}
return className;
}
@ -1797,7 +1802,7 @@ export function getInitialClassNameValue(context: StylingContext): string {
*/
export function getInitialStyleStringValue(context: StylingContext): string {
const initialStyleValues = context[StylingIndex.InitialStyleValuesPosition];
let styleString = initialStyleValues[InitialStylingValuesIndex.InitialClassesStringPosition];
let styleString = initialStyleValues[InitialStylingValuesIndex.CachedStringValuePosition];
if (styleString === null) {
styleString = '';
for (let i = InitialStylingValuesIndex.KeyValueStartPosition; i < initialStyleValues.length;
@ -1807,7 +1812,7 @@ export function getInitialStyleStringValue(context: StylingContext): string {
styleString += (styleString.length ? ';' : '') + `${initialStyleValues[i]}:${value}`;
}
}
initialStyleValues[InitialStylingValuesIndex.InitialClassesStringPosition] = styleString;
initialStyleValues[InitialStylingValuesIndex.CachedStringValuePosition] = styleString;
}
return styleString;
}
@ -1956,3 +1961,30 @@ function registerMultiMapEntry(
}
cachedValues.push(0, startPosition, null, count);
}
/**
* Inserts or updates an existing entry in the provided `staticStyles` collection.
*
* @param index the index representing an existing styling entry in the collection:
* if provided (numeric): then it will update the existing entry at the given position
* if null: then it will insert a new entry within the collection
* @param staticStyles a collection of style or class entries where the value will
* be inserted or patched
* @param prop the property value of the entry (e.g. `width` (styles) or `foo` (classes))
* @param value the styling value of the entry (e.g. `absolute` (styles) or `true` (classes))
* @param directiveOwnerIndex the directive owner index value of the styling source responsible
* for these styles (see `interfaces/styling.ts#directives` for more info)
* @returns the index of the updated or new entry within the collection
*/
function addOrUpdateStaticStyle(
index: number | null, staticStyles: InitialStylingValues, prop: string,
value: string | boolean | null, directiveOwnerIndex: number) {
if (index === null) {
index = staticStyles.length;
staticStyles.push(null, null, null);
staticStyles[index + InitialStylingValuesIndex.PropOffset] = prop;
}
staticStyles[index + InitialStylingValuesIndex.ValueOffset] = value;
staticStyles[index + InitialStylingValuesIndex.DirectiveOwnerOffset] = directiveOwnerIndex;
return index;
}

View File

@ -14,7 +14,7 @@ import {LContext} from '../interfaces/context';
import {AttributeMarker, TAttributes, TNode, TNodeFlags} from '../interfaces/node';
import {PlayState, Player, PlayerContext, PlayerIndex} from '../interfaces/player';
import {RElement} from '../interfaces/renderer';
import {InitialStylingValues, StylingContext, StylingFlags, StylingIndex} from '../interfaces/styling';
import {DirectiveRegistryValuesIndex, InitialStylingValues, StylingContext, StylingFlags, StylingIndex} from '../interfaces/styling';
import {HEADER_OFFSET, HOST, LView, RootContext} from '../interfaces/view';
import {getTNode, isStylingContext} from '../util/view_utils';
@ -37,13 +37,48 @@ export function createEmptyStylingContext(
[0], // CachedMultiStyleValue
null, // PlayerContext
];
// whenever a context is created there is always a `null` directive
// that is registered (which is a placeholder for the "template").
allocateDirectiveIntoContext(context, null);
return context;
}
export function allocateDirectiveIntoContext(context: StylingContext, directiveRef: any | null) {
/**
* Allocates (registers) a directive into the directive registry within the provided styling
* context.
*
* For each and every `[style]`, `[style.prop]`, `[class]`, `[class.name]` binding
* (as well as static style and class attributes) a directive, component or template
* is marked as the owner. When an owner is determined (this happens when the template
* is first passed over) the directive owner is allocated into the styling context. When
* this happens, each owner gets its own index value. This then ensures that once any
* style and/or class binding are assigned into the context then they are marked to
* that directive's index value.
*
* @param context the target StylingContext
* @param directiveRef the directive that will be allocated into the context
* @returns the index where the directive was inserted into
*/
export function allocateDirectiveIntoContext(
context: StylingContext, directiveRef: any | null): number {
// this is a new directive which we have not seen yet.
context[StylingIndex.DirectiveRegistryPosition].push(directiveRef, -1, false, null);
const dirs = context[StylingIndex.DirectiveRegistryPosition];
const i = dirs.length;
// we preemptively make space into the directives array and then
// assign values slot-by-slot to ensure that if the directive ordering
// changes then it will still function
dirs.push(null, null, null, null);
dirs[i + DirectiveRegistryValuesIndex.DirectiveValueOffset] = directiveRef;
dirs[i + DirectiveRegistryValuesIndex.DirtyFlagOffset] = false;
dirs[i + DirectiveRegistryValuesIndex.StyleSanitizerOffset] = null;
// -1 is used to signal that the directive has been allocated, but
// no actual style or class bindings have been registered yet...
dirs[i + DirectiveRegistryValuesIndex.SinglePropValuesIndexOffset] = -1;
return i;
}
/**
@ -228,11 +263,3 @@ export function allocPlayerContext(data: StylingContext): PlayerContext {
export function throwInvalidRefError() {
throw new Error('Only elements that exist in an Angular application can be used for animations');
}
export function hasStyling(attrs: TAttributes): boolean {
for (let i = 0; i < attrs.length; i++) {
const attr = attrs[i];
if (attr == AttributeMarker.Classes || attr == AttributeMarker.Styles) return true;
}
return false;
}

View File

@ -0,0 +1,107 @@
/**
* @license
* Copyright Google Inc. All Rights Reserved.
*
* Use of this source code is governed by an MIT-style license that can be
* found in the LICENSE file at https://angular.io/license
*/
import {AttributeMarker, TAttributes} from '../interfaces/node';
import {NG_PROJECT_AS_ATTR_NAME} from '../interfaces/projection';
import {ProceduralRenderer3, RElement, isProceduralRenderer} from '../interfaces/renderer';
import {RENDERER} from '../interfaces/view';
import {getLView} from '../state';
import {isAnimationProp} from '../styling/util';
/**
* Assigns all attribute values to the provided element via the inferred renderer.
*
* This function accepts two forms of attribute entries:
*
* default: (key, value):
* attrs = [key1, value1, key2, value2]
*
* namespaced: (NAMESPACE_MARKER, uri, name, value)
* attrs = [NAMESPACE_MARKER, uri, name, value, NAMESPACE_MARKER, uri, name, value]
*
* The `attrs` array can contain a mix of both the default and namespaced entries.
* The "default" values are set without a marker, but if the function comes across
* a marker value then it will attempt to set a namespaced value. If the marker is
* not of a namespaced value then the function will quit and return the index value
* where it stopped during the iteration of the attrs array.
*
* See [AttributeMarker] to understand what the namespace marker value is.
*
* Note that this instruction does not support assigning style and class values to
* an element. See `elementStart` and `elementHostAttrs` to learn how styling values
* are applied to an element.
*
* @param native The element that the attributes will be assigned to
* @param attrs The attribute array of values that will be assigned to the element
* @returns the index value that was last accessed in the attributes array
*/
export function setUpAttributes(native: RElement, attrs: TAttributes): number {
const renderer = getLView()[RENDERER];
const isProc = isProceduralRenderer(renderer);
let i = 0;
while (i < attrs.length) {
const value = attrs[i];
if (typeof value === 'number') {
// only namespaces are supported. Other value types (such as style/class
// entries) are not supported in this function.
if (value !== AttributeMarker.NamespaceURI) {
break;
}
// we just landed on the marker value ... therefore
// we should skip to the next entry
i++;
const namespaceURI = attrs[i++] as string;
const attrName = attrs[i++] as string;
const attrVal = attrs[i++] as string;
ngDevMode && ngDevMode.rendererSetAttribute++;
isProc ?
(renderer as ProceduralRenderer3).setAttribute(native, attrName, attrVal, namespaceURI) :
native.setAttributeNS(namespaceURI, attrName, attrVal);
} else {
/// attrName is string;
const attrName = value as string;
const attrVal = attrs[++i];
if (attrName !== NG_PROJECT_AS_ATTR_NAME) {
// Standard attributes
ngDevMode && ngDevMode.rendererSetAttribute++;
if (isAnimationProp(attrName)) {
if (isProc) {
(renderer as ProceduralRenderer3).setProperty(native, attrName, attrVal);
}
} else {
isProc ?
(renderer as ProceduralRenderer3)
.setAttribute(native, attrName as string, attrVal as string) :
native.setAttribute(attrName as string, attrVal as string);
}
}
i++;
}
}
// another piece of code may iterate over the same attributes array. Therefore
// it may be helpful to return the exact spot where the attributes array exited
// whether by running into an unsupported marker or if all the static values were
// iterated over.
return i;
}
export function attrsStylingIndexOf(attrs: TAttributes, startIndex: number): number {
for (let i = startIndex; i < attrs.length; i++) {
const val = attrs[i];
if (val === AttributeMarker.Classes || val === AttributeMarker.Styles) {
return i;
}
}
return -1;
}