refactor(ivy): delete `ɵɵelementHostAttrs` instruction (#34717)
PR Close #34717
This commit is contained in:
parent
2227d471a4
commit
da7e362bce
|
@ -17,13 +17,15 @@ import {assertComponentType} from './assert';
|
|||
import {getComponentDef} from './definition';
|
||||
import {diPublicInInjector, getOrCreateNodeInjectorForNode} from './di';
|
||||
import {registerPostOrderHooks} from './hooks';
|
||||
import {CLEAN_PROMISE, addHostBindingsToExpandoInstructions, addToViewTree, createLView, createTView, getOrCreateTComponentView, getOrCreateTNode, growHostVarsSpace, initNodeFlags, instantiateRootComponent, invokeHostBindingsInCreationMode, locateHostElement, markAsComponentHost, refreshView, renderView} from './instructions/shared';
|
||||
import {CLEAN_PROMISE, addHostBindingsToExpandoInstructions, addToViewTree, createLView, createTView, getOrCreateTComponentView, getOrCreateTNode, growHostVarsSpace, initTNodeFlags, instantiateRootComponent, invokeHostBindingsInCreationMode, locateHostElement, markAsComponentHost, refreshView, renderInitialStyling, renderView} from './instructions/shared';
|
||||
import {registerInitialStylingOnTNode} from './instructions/styling';
|
||||
import {ComponentDef, ComponentType, RenderFlags} from './interfaces/definition';
|
||||
import {TElementNode, TNode, TNodeType} from './interfaces/node';
|
||||
import {PlayerHandler} from './interfaces/player';
|
||||
import {RElement, Renderer3, RendererFactory3, domRendererFactory3} from './interfaces/renderer';
|
||||
import {CONTEXT, HEADER_OFFSET, LView, LViewFlags, RootContext, RootContextFlags, TVIEW, TViewType} from './interfaces/view';
|
||||
import {enterView, getPreviousOrParentTNode, incrementActiveDirectiveId, leaveView, setActiveHostElement} from './state';
|
||||
import {setUpAttributes} from './util/attrs_utils';
|
||||
import {publishDefaultGlobalUtils} from './util/global_utils';
|
||||
import {defaultScheduler, stringifyForError} from './util/misc_utils';
|
||||
import {getRootContext} from './util/view_traversal_utils';
|
||||
|
@ -171,6 +173,14 @@ export function createRootComponentView(
|
|||
ngDevMode && assertDataInRange(rootView, 0 + HEADER_OFFSET);
|
||||
rootView[0 + HEADER_OFFSET] = rNode;
|
||||
const tNode: TElementNode = getOrCreateTNode(tView, null, 0, TNodeType.Element, null, null);
|
||||
const mergedAttrs = tNode.mergedAttrs = def.hostAttrs;
|
||||
if (mergedAttrs !== null) {
|
||||
registerInitialStylingOnTNode(tNode, mergedAttrs, 0);
|
||||
if (rNode !== null) {
|
||||
setUpAttributes(renderer, rNode, mergedAttrs);
|
||||
renderInitialStyling(renderer, rNode, tNode, false);
|
||||
}
|
||||
}
|
||||
const componentView = createLView(
|
||||
rootView, getOrCreateTComponentView(def), null,
|
||||
def.onPush ? LViewFlags.Dirty : LViewFlags.CheckAlways, rootView[HEADER_OFFSET], tNode,
|
||||
|
@ -179,7 +189,7 @@ export function createRootComponentView(
|
|||
if (tView.firstCreatePass) {
|
||||
diPublicInInjector(getOrCreateNodeInjectorForNode(tNode, rootView), tView, def.type);
|
||||
markAsComponentHost(tView, tNode);
|
||||
initNodeFlags(tNode, rootView.length, 1);
|
||||
initTNodeFlags(tNode, rootView.length, 1);
|
||||
}
|
||||
|
||||
addToViewTree(rootView, componentView);
|
||||
|
@ -216,7 +226,8 @@ export function createRootComponent<T>(
|
|||
// part of the `hostAttrs`.
|
||||
// The check for componentDef.hostBindings is wrong since now some directives may not
|
||||
// have componentDef.hostBindings but they still need to process hostVars and hostAttrs
|
||||
if (tView.firstCreatePass && (componentDef.hostBindings || componentDef.hostAttrs !== null)) {
|
||||
if (tView.firstCreatePass &&
|
||||
(componentDef.hostBindings !== null || componentDef.hostAttrs !== null)) {
|
||||
const elementIndex = rootTNode.index - HEADER_OFFSET;
|
||||
setActiveHostElement(elementIndex);
|
||||
incrementActiveDirectiveId();
|
||||
|
@ -225,9 +236,7 @@ export function createRootComponent<T>(
|
|||
addHostBindingsToExpandoInstructions(rootTView, componentDef);
|
||||
growHostVarsSpace(rootTView, rootLView, componentDef.hostVars);
|
||||
|
||||
const expando = tView.expandoInstructions !;
|
||||
invokeHostBindingsInCreationMode(
|
||||
componentDef, expando, component, rootTNode, tView.firstCreatePass);
|
||||
invokeHostBindingsInCreationMode(componentDef, component, rootTNode);
|
||||
|
||||
setActiveHostElement(null);
|
||||
}
|
||||
|
|
|
@ -7,7 +7,7 @@
|
|||
*/
|
||||
|
||||
import {assertDataInRange, assertDefined, assertEqual} from '../../util/assert';
|
||||
import {assertHasParent} from '../assert';
|
||||
import {assertFirstCreatePass, assertHasParent} from '../assert';
|
||||
import {attachPatchData} from '../context_discovery';
|
||||
import {registerPostOrderHooks} from '../hooks';
|
||||
import {TAttributes, TElementNode, TNode, TNodeFlags, TNodeType} from '../interfaces/node';
|
||||
|
@ -28,20 +28,21 @@ import {registerInitialStylingOnTNode} from './styling';
|
|||
function elementStartFirstCreatePass(
|
||||
index: number, tView: TView, lView: LView, native: RElement, name: string,
|
||||
attrsIndex?: number | null, localRefsIndex?: number): TElementNode {
|
||||
ngDevMode && assertFirstCreatePass(tView);
|
||||
ngDevMode && ngDevMode.firstCreatePass++;
|
||||
|
||||
const tViewConsts = tView.consts;
|
||||
const attrs = getConstant<TAttributes>(tViewConsts, attrsIndex);
|
||||
const tNode = getOrCreateTNode(tView, lView[T_HOST], index, TNodeType.Element, name, attrs);
|
||||
|
||||
if (attrs !== null) {
|
||||
registerInitialStylingOnTNode(tNode, attrs, 0);
|
||||
}
|
||||
|
||||
const hasDirectives =
|
||||
resolveDirectives(tView, lView, tNode, getConstant<string[]>(tViewConsts, localRefsIndex));
|
||||
ngDevMode && warnAboutUnknownElement(lView, native, tNode, hasDirectives);
|
||||
|
||||
if (tNode.mergedAttrs !== null) {
|
||||
registerInitialStylingOnTNode(tNode, tNode.mergedAttrs, 0);
|
||||
}
|
||||
|
||||
if (tView.queries !== null) {
|
||||
tView.queries.elementStart(tView, tNode);
|
||||
}
|
||||
|
@ -83,9 +84,9 @@ export function ɵɵelementStart(
|
|||
tView.data[adjustedIndex] as TElementNode;
|
||||
setPreviousOrParentTNode(tNode, true);
|
||||
|
||||
const attrs = tNode.attrs;
|
||||
if (attrs != null) {
|
||||
setUpAttributes(renderer, native, attrs);
|
||||
const mergedAttrs = tNode.mergedAttrs;
|
||||
if (mergedAttrs !== null) {
|
||||
setUpAttributes(renderer, native, mergedAttrs);
|
||||
}
|
||||
if ((tNode.flags & TNodeFlags.hasInitialStyling) === TNodeFlags.hasInitialStyling) {
|
||||
renderInitialStyling(renderer, native, tNode, false);
|
||||
|
@ -106,7 +107,7 @@ export function ɵɵelementStart(
|
|||
createDirectivesInstances(tView, lView, tNode);
|
||||
executeContentQueries(tView, tNode, lView);
|
||||
}
|
||||
if (localRefsIndex != null) {
|
||||
if (localRefsIndex !== null) {
|
||||
saveResolvedLocalsInData(lView, tNode);
|
||||
}
|
||||
}
|
||||
|
@ -169,78 +170,6 @@ export function ɵɵelement(
|
|||
ɵɵelementEnd();
|
||||
}
|
||||
|
||||
/**
|
||||
* Assign static attribute values to a host element.
|
||||
*
|
||||
* This instruction will assign static attribute values as well as class and style
|
||||
* values to an element within the host bindings function. Since attribute values
|
||||
* can consist of different types of values, the `attrs` array must include the values in
|
||||
* the following format:
|
||||
*
|
||||
* attrs = [
|
||||
* // static attributes (like `title`, `name`, `id`...)
|
||||
* attr1, value1, attr2, value,
|
||||
*
|
||||
* // a single namespace value (like `x:id`)
|
||||
* NAMESPACE_MARKER, namespaceUri1, name1, value1,
|
||||
*
|
||||
* // another single namespace value (like `x:name`)
|
||||
* NAMESPACE_MARKER, namespaceUri2, name2, value2,
|
||||
*
|
||||
* // a series of CSS classes that will be applied to the element (no spaces)
|
||||
* CLASSES_MARKER, class1, class2, class3,
|
||||
*
|
||||
* // a series of CSS styles (property + value) that will be applied to the element
|
||||
* STYLES_MARKER, prop1, value1, prop2, value2
|
||||
* ]
|
||||
*
|
||||
* All non-class and non-style attributes must be defined at the start of the list
|
||||
* first before all class and style values are set. When there is a change in value
|
||||
* type (like when classes and styles are introduced) a marker must be used to separate
|
||||
* the entries. The marker values themselves are set via entries found in the
|
||||
* [AttributeMarker] enum.
|
||||
*
|
||||
* NOTE: This instruction is meant to used from `hostBindings` function only.
|
||||
*
|
||||
* @param directive A directive instance the styling is associated with.
|
||||
* @param attrs An array of static values (attributes, classes and styles) with the correct marker
|
||||
* values.
|
||||
*
|
||||
* @codeGenApi
|
||||
*/
|
||||
export function ɵɵelementHostAttrs(attrs: TAttributes) {
|
||||
const hostElementIndex = getSelectedIndex();
|
||||
const lView = getLView();
|
||||
const tView = lView[TVIEW];
|
||||
const tNode = getTNode(hostElementIndex, lView);
|
||||
|
||||
// non-element nodes (e.g. `<ng-container>`) are not rendered as actual
|
||||
// element nodes and adding styles/classes on to them will cause runtime
|
||||
// errors...
|
||||
if (tNode.type === TNodeType.Element) {
|
||||
const native = getNativeByTNode(tNode, lView) as RElement;
|
||||
// TODO(misko-next): setup attributes need to be moved out of `ɵɵelementHostAttrs`
|
||||
const lastAttrIndex = setUpAttributes(lView[RENDERER], native, attrs);
|
||||
if (tView.firstCreatePass) {
|
||||
const stylingNeedsToBeRendered = registerInitialStylingOnTNode(tNode, attrs, lastAttrIndex);
|
||||
|
||||
// this is only called during the first template pass in the
|
||||
// event that this current directive assigned initial style/class
|
||||
// host attribute values to the element. Because initial styling
|
||||
// values are applied before directives are first rendered (within
|
||||
// `createElement`) this means that initial styling for any directives
|
||||
// still needs to be applied. Note that this will only happen during
|
||||
// the first template pass and not each time a directive applies its
|
||||
// attribute values to the element.
|
||||
if (stylingNeedsToBeRendered) {
|
||||
const renderer = lView[RENDERER];
|
||||
// TODO(misko-next): Styling initialization should move out of `ɵɵelementHostAttrs`
|
||||
renderInitialStyling(renderer, native, tNode, true);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function setDirectiveStylingInput(
|
||||
context: TStylingContext | StylingMapArray | null, lView: LView,
|
||||
stylingInputs: (string | number)[], propName: string) {
|
||||
|
|
|
@ -154,31 +154,32 @@ export const TViewConstructor = class TView implements ITView {
|
|||
|
||||
export const TNodeConstructor = class TNode implements ITNode {
|
||||
constructor(
|
||||
public tView_: TView, //
|
||||
public type: TNodeType, //
|
||||
public index: number, //
|
||||
public injectorIndex: number, //
|
||||
public directiveStart: number, //
|
||||
public directiveEnd: number, //
|
||||
public propertyBindings: number[]|null, //
|
||||
public flags: TNodeFlags, //
|
||||
public providerIndexes: TNodeProviderIndexes, //
|
||||
public tagName: string|null, //
|
||||
public attrs: (string|AttributeMarker|(string|SelectorFlags)[])[]|null, //
|
||||
public localNames: (string|number)[]|null, //
|
||||
public initialInputs: (string[]|null)[]|null|undefined, //
|
||||
public inputs: PropertyAliases|null, //
|
||||
public outputs: PropertyAliases|null, //
|
||||
public tViews: ITView|ITView[]|null, //
|
||||
public next: ITNode|null, //
|
||||
public projectionNext: ITNode|null, //
|
||||
public child: ITNode|null, //
|
||||
public parent: TElementNode|TContainerNode|null, //
|
||||
public projection: number|(ITNode|RNode[])[]|null, //
|
||||
public styles: TStylingContext|null, //
|
||||
public classes: TStylingContext|null, //
|
||||
public classBindings: TStylingRange, //
|
||||
public styleBindings: TStylingRange, //
|
||||
public tView_: TView, //
|
||||
public type: TNodeType, //
|
||||
public index: number, //
|
||||
public injectorIndex: number, //
|
||||
public directiveStart: number, //
|
||||
public directiveEnd: number, //
|
||||
public propertyBindings: number[]|null, //
|
||||
public flags: TNodeFlags, //
|
||||
public providerIndexes: TNodeProviderIndexes, //
|
||||
public tagName: string|null, //
|
||||
public attrs: (string|AttributeMarker|(string|SelectorFlags)[])[]|null, //
|
||||
public mergedAttrs: (string|AttributeMarker|(string|SelectorFlags)[])[]|null, //
|
||||
public localNames: (string|number)[]|null, //
|
||||
public initialInputs: (string[]|null)[]|null|undefined, //
|
||||
public inputs: PropertyAliases|null, //
|
||||
public outputs: PropertyAliases|null, //
|
||||
public tViews: ITView|ITView[]|null, //
|
||||
public next: ITNode|null, //
|
||||
public projectionNext: ITNode|null, //
|
||||
public child: ITNode|null, //
|
||||
public parent: TElementNode|TContainerNode|null, //
|
||||
public projection: number|(ITNode|RNode[])[]|null, //
|
||||
public styles: TStylingContext|null, //
|
||||
public classes: TStylingContext|null, //
|
||||
public classBindings: TStylingRange, //
|
||||
public styleBindings: TStylingRange, //
|
||||
) {}
|
||||
|
||||
get type_(): string {
|
||||
|
|
|
@ -20,7 +20,7 @@ import {attachPatchData} from '../context_discovery';
|
|||
import {getFactoryDef} from '../definition';
|
||||
import {diPublicInInjector, getNodeInjectable, getOrCreateNodeInjectorForNode} from '../di';
|
||||
import {throwMultipleComponentError} from '../errors';
|
||||
import {executeCheckHooks, executeInitAndCheckHooks, incrementInitPhaseFlags, registerPreOrderHooks} from '../hooks';
|
||||
import {executeCheckHooks, executeInitAndCheckHooks, incrementInitPhaseFlags} from '../hooks';
|
||||
import {ACTIVE_INDEX, ActiveIndexFlag, CONTAINER_HEADER_OFFSET, LContainer, MOVED_VIEWS} from '../interfaces/container';
|
||||
import {ComponentDef, ComponentTemplate, DirectiveDef, DirectiveDefListOrFactory, PipeDefListOrFactory, RenderFlags, ViewQueriesFunction} from '../interfaces/definition';
|
||||
import {INJECTOR_BLOOM_PARENT_SIZE, NodeInjectorFactory} from '../interfaces/injector';
|
||||
|
@ -28,20 +28,18 @@ import {AttributeMarker, InitialInputData, InitialInputs, LocalRefExtractor, Pro
|
|||
import {RComment, RElement, RNode, RText, Renderer3, RendererFactory3, isProceduralRenderer} from '../interfaces/renderer';
|
||||
import {SanitizerFn} from '../interfaces/sanitization';
|
||||
import {isComponentDef, isComponentHost, isContentQueryHost, isLContainer, isRootView} from '../interfaces/type_checks';
|
||||
import {CHILD_HEAD, CHILD_TAIL, CLEANUP, CONTEXT, DECLARATION_COMPONENT_VIEW, DECLARATION_VIEW, ExpandoInstructions, 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 {isNodeMatchingSelectorList} from '../node_selector_matcher';
|
||||
import {ActiveElementFlags, enterView, executeElementExitFn, getBindingsEnabled, getCheckNoChangesMode, getIsParent, getPreviousOrParentTNode, getSelectedIndex, hasActiveElementFlag, incrementActiveDirectiveId, leaveView, leaveViewProcessExit, setActiveHostElement, setBindingIndex, setBindingRoot, setCheckNoChangesMode, setCurrentDirectiveDef, setCurrentQueryIndex, setPreviousOrParentTNode, setSelectedIndex} from '../state';
|
||||
import {ActiveElementFlags, enterView, executeElementExitFn, getBindingsEnabled, getCheckNoChangesMode, getIsParent, getPreviousOrParentTNode, getSelectedIndex, hasActiveElementFlag, incrementActiveDirectiveId, leaveView, leaveViewProcessExit, setActiveHostElement, setBindingIndex, setBindingRoot, setCheckNoChangesMode, setCurrentQueryIndex, setPreviousOrParentTNode, setSelectedIndex} from '../state';
|
||||
import {renderStylingMap, writeStylingValueDirectly} from '../styling/bindings';
|
||||
import {NO_CHANGE} from '../tokens';
|
||||
import {isAnimationProp} from '../util/attrs_utils';
|
||||
import {isAnimationProp, mergeHostAttrs} from '../util/attrs_utils';
|
||||
import {INTERPOLATION_DELIMITER, renderStringify, stringifyForError} from '../util/misc_utils';
|
||||
import {getInitialStylingValue} from '../util/styling_utils';
|
||||
import {getLViewParent} from '../util/view_traversal_utils';
|
||||
import {getComponentLViewByIndex, getNativeByIndex, getNativeByTNode, getTNode, isCreationMode, readPatchedLView, resetPreOrderHookFlags, unwrapRNode, viewAttachedToChangeDetector} from '../util/view_utils';
|
||||
|
||||
import {getComponentLViewByIndex, getNativeByIndex, getNativeByTNode, getTNode, isCreationMode, readPatchedLView, resetPreOrderHookFlags, viewAttachedToChangeDetector} from '../util/view_utils';
|
||||
import {selectIndexInternal} from './advance';
|
||||
import {ɵɵelementHostAttrs} from './element';
|
||||
import {LCleanup, LViewBlueprint, MatchesArray, TCleanup, TNodeConstructor, TNodeInitialInputs, TNodeLocalNames, TViewComponents, TViewConstructor, attachLContainerDebug, attachLViewDebug, cloneToLViewFromTViewBlueprint, cloneToTViewData} from './lview_debug';
|
||||
|
||||
|
||||
|
@ -828,6 +826,7 @@ export function createTNode(
|
|||
0, // providerIndexes: TNodeProviderIndexes
|
||||
tagName, // tagName: string|null
|
||||
attrs, // attrs: (string|AttributeMarker|(string|SelectorFlags)[])[]|null
|
||||
null, // mergedAttrs
|
||||
null, // localNames: (string|number)[]|null
|
||||
undefined, // initialInputs: (string[]|null)[]|null|undefined
|
||||
null, // inputs: PropertyAliases|null
|
||||
|
@ -854,6 +853,7 @@ export function createTNode(
|
|||
providerIndexes: 0,
|
||||
tagName: tagName,
|
||||
attrs: attrs,
|
||||
mergedAttrs: null,
|
||||
localNames: null,
|
||||
initialInputs: undefined,
|
||||
inputs: null,
|
||||
|
@ -1115,64 +1115,69 @@ export function resolveDirectives(
|
|||
// tsickle.
|
||||
ngDevMode && assertFirstCreatePass(tView);
|
||||
|
||||
if (!getBindingsEnabled()) return false;
|
||||
|
||||
const directives: DirectiveDef<any>[]|null = findDirectiveMatches(tView, lView, tNode);
|
||||
const exportsMap: ({[key: string]: number} | null) = localRefs === null ? null : {'': -1};
|
||||
let hasDirectives = false;
|
||||
if (getBindingsEnabled()) {
|
||||
const directiveDefs: DirectiveDef<any>[]|null = findDirectiveDefMatches(tView, lView, tNode);
|
||||
const exportsMap: ({[key: string]: number} | null) = localRefs === null ? null : {'': -1};
|
||||
|
||||
if (directives !== null) {
|
||||
let totalDirectiveHostVars = 0;
|
||||
hasDirectives = true;
|
||||
initNodeFlags(tNode, tView.data.length, directives.length);
|
||||
// When the same token is provided by several directives on the same node, some rules apply in
|
||||
// the viewEngine:
|
||||
// - viewProviders have priority over providers
|
||||
// - the last directive in NgModule.declarations has priority over the previous one
|
||||
// So to match these rules, the order in which providers are added in the arrays is very
|
||||
// important.
|
||||
for (let i = 0; i < directives.length; i++) {
|
||||
const def = directives[i];
|
||||
if (def.providersResolver) def.providersResolver(def);
|
||||
}
|
||||
generateExpandoInstructionBlock(tView, tNode, directives.length);
|
||||
let preOrderHooksFound = false;
|
||||
let preOrderCheckHooksFound = false;
|
||||
for (let i = 0; i < directives.length; i++) {
|
||||
const def = directives[i];
|
||||
if (directiveDefs !== null) {
|
||||
let totalDirectiveHostVars = 0;
|
||||
hasDirectives = true;
|
||||
initTNodeFlags(tNode, tView.data.length, directiveDefs.length);
|
||||
// When the same token is provided by several directives on the same node, some rules apply in
|
||||
// the viewEngine:
|
||||
// - viewProviders have priority over providers
|
||||
// - the last directive in NgModule.declarations has priority over the previous one
|
||||
// So to match these rules, the order in which providers are added in the arrays is very
|
||||
// important.
|
||||
for (let i = 0; i < directiveDefs.length; i++) {
|
||||
const def = directiveDefs[i];
|
||||
if (def.providersResolver) def.providersResolver(def);
|
||||
}
|
||||
generateExpandoInstructionBlock(tView, tNode, directiveDefs.length);
|
||||
let preOrderHooksFound = false;
|
||||
let preOrderCheckHooksFound = false;
|
||||
for (let i = 0; i < directiveDefs.length; i++) {
|
||||
const def = directiveDefs[i];
|
||||
// Merge the attrs in the order of matches. This assumes that the first directive is the
|
||||
// component itself, so that the component has the least priority.
|
||||
tNode.mergedAttrs = mergeHostAttrs(tNode.mergedAttrs, def.hostAttrs);
|
||||
|
||||
baseResolveDirective(tView, lView, def);
|
||||
baseResolveDirective(tView, lView, def);
|
||||
|
||||
saveNameToExportMap(tView.data !.length - 1, def, exportsMap);
|
||||
saveNameToExportMap(tView.data !.length - 1, def, exportsMap);
|
||||
|
||||
if (def.contentQueries !== null) tNode.flags |= TNodeFlags.hasContentQuery;
|
||||
if (def.hostBindings !== null || def.hostAttrs !== null || def.hostVars !== 0)
|
||||
tNode.flags |= TNodeFlags.hasHostBindings;
|
||||
if (def.contentQueries !== null) tNode.flags |= TNodeFlags.hasContentQuery;
|
||||
if (def.hostBindings !== null || def.hostAttrs !== null || def.hostVars !== 0)
|
||||
tNode.flags |= TNodeFlags.hasHostBindings;
|
||||
|
||||
// Only push a node index into the preOrderHooks array if this is the first
|
||||
// pre-order hook found on this node.
|
||||
if (!preOrderHooksFound && (def.onChanges || def.onInit || def.doCheck)) {
|
||||
// We will push the actual hook function into this array later during dir instantiation.
|
||||
// We cannot do it now because we must ensure hooks are registered in the same
|
||||
// order that directives are created (i.e. injection order).
|
||||
(tView.preOrderHooks || (tView.preOrderHooks = [])).push(tNode.index - HEADER_OFFSET);
|
||||
preOrderHooksFound = true;
|
||||
// Only push a node index into the preOrderHooks array if this is the first
|
||||
// pre-order hook found on this node.
|
||||
if (!preOrderHooksFound && (def.onChanges || def.onInit || def.doCheck)) {
|
||||
// We will push the actual hook function into this array later during dir instantiation.
|
||||
// We cannot do it now because we must ensure hooks are registered in the same
|
||||
// order that directives are created (i.e. injection order).
|
||||
(tView.preOrderHooks || (tView.preOrderHooks = [])).push(tNode.index - HEADER_OFFSET);
|
||||
preOrderHooksFound = true;
|
||||
}
|
||||
|
||||
if (!preOrderCheckHooksFound && (def.onChanges || def.doCheck)) {
|
||||
(tView.preOrderCheckHooks || (tView.preOrderCheckHooks = [
|
||||
])).push(tNode.index - HEADER_OFFSET);
|
||||
preOrderCheckHooksFound = true;
|
||||
}
|
||||
|
||||
addHostBindingsToExpandoInstructions(tView, def);
|
||||
totalDirectiveHostVars += def.hostVars;
|
||||
}
|
||||
|
||||
if (!preOrderCheckHooksFound && (def.onChanges || def.doCheck)) {
|
||||
(tView.preOrderCheckHooks || (tView.preOrderCheckHooks = [
|
||||
])).push(tNode.index - HEADER_OFFSET);
|
||||
preOrderCheckHooksFound = true;
|
||||
}
|
||||
|
||||
addHostBindingsToExpandoInstructions(tView, def);
|
||||
totalDirectiveHostVars += def.hostVars;
|
||||
initializeInputAndOutputAliases(tView, tNode);
|
||||
growHostVarsSpace(tView, lView, totalDirectiveHostVars);
|
||||
}
|
||||
|
||||
initializeInputAndOutputAliases(tView, tNode);
|
||||
growHostVarsSpace(tView, lView, totalDirectiveHostVars);
|
||||
if (exportsMap) cacheMatchingLocalNames(tNode, localRefs, exportsMap);
|
||||
}
|
||||
if (exportsMap) cacheMatchingLocalNames(tNode, localRefs, exportsMap);
|
||||
// Merge the template attrs last so that they have the highest priority.
|
||||
tNode.mergedAttrs = mergeHostAttrs(tNode.mergedAttrs, tNode.attrs);
|
||||
return hasDirectives;
|
||||
}
|
||||
|
||||
|
@ -1187,9 +1192,7 @@ export function addHostBindingsToExpandoInstructions(
|
|||
ngDevMode && assertFirstCreatePass(tView);
|
||||
const expando = tView.expandoInstructions !;
|
||||
// TODO(misko): PERF we are adding `hostBindings` even if there is nothing to add! This is
|
||||
// suboptimal for performance. See `currentDirectiveIndex` comment in
|
||||
// `setHostBindingsByExecutingExpandoInstructions` for details.
|
||||
// TODO(misko): PERF `def.hostBindings` may be null,
|
||||
// suboptimal for performance. `def.hostBindings` may be null,
|
||||
// but we still need to push null to the array as a placeholder
|
||||
// to ensure the directive counter is incremented (so host
|
||||
// binding functions always line up with the corrective directive).
|
||||
|
@ -1277,7 +1280,7 @@ function invokeDirectivesHostBindings(tView: TView, viewData: LView, tNode: TNod
|
|||
// It is important that this be called first before the actual instructions
|
||||
// are run because this way the first directive ID value is not zero.
|
||||
incrementActiveDirectiveId();
|
||||
invokeHostBindingsInCreationMode(def, expando, directive, tNode, firstCreatePass);
|
||||
invokeHostBindingsInCreationMode(def, directive, tNode);
|
||||
} else if (firstCreatePass) {
|
||||
expando.push(null);
|
||||
}
|
||||
|
@ -1287,22 +1290,13 @@ function invokeDirectivesHostBindings(tView: TView, viewData: LView, tNode: TNod
|
|||
}
|
||||
}
|
||||
|
||||
// TODO(COMMIT): jsdoc
|
||||
export function invokeHostBindingsInCreationMode(
|
||||
def: DirectiveDef<any>, expando: ExpandoInstructions, directive: any, tNode: TNode,
|
||||
firstCreatePass: boolean) {
|
||||
const previousExpandoLength = expando.length;
|
||||
setCurrentDirectiveDef(def);
|
||||
const elementIndex = tNode.index - HEADER_OFFSET;
|
||||
// TODO(misko-next): This is a temporary work around for the fact that we moved the information
|
||||
// from instruction to declaration. The workaround is to just call the instruction as if it was
|
||||
// part of the `hostAttrs`.
|
||||
if (def.hostAttrs !== null) {
|
||||
ɵɵelementHostAttrs(def.hostAttrs);
|
||||
}
|
||||
def: DirectiveDef<any>, directive: any, tNode: TNode) {
|
||||
if (def.hostBindings !== null) {
|
||||
const elementIndex = tNode.index - HEADER_OFFSET;
|
||||
def.hostBindings !(RenderFlags.Create, directive, elementIndex);
|
||||
}
|
||||
setCurrentDirectiveDef(null);
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -1331,7 +1325,7 @@ export function generateExpandoInstructionBlock(
|
|||
* Matches the current node against all available selectors.
|
||||
* If a component is matched (at most one), it is returned in first position in the array.
|
||||
*/
|
||||
function findDirectiveMatches(
|
||||
function findDirectiveDefMatches(
|
||||
tView: TView, viewData: LView,
|
||||
tNode: TElementNode | TContainerNode | TElementContainerNode): DirectiveDef<any>[]|null {
|
||||
ngDevMode && assertFirstCreatePass(tView);
|
||||
|
@ -1413,7 +1407,7 @@ function saveNameToExportMap(
|
|||
* the directive count to 0, and adding the isComponent flag.
|
||||
* @param index the initial index
|
||||
*/
|
||||
export function initNodeFlags(tNode: TNode, index: number, numberOfDirectives: number) {
|
||||
export function initTNodeFlags(tNode: TNode, index: number, numberOfDirectives: number) {
|
||||
ngDevMode && assertNotEqual(
|
||||
numberOfDirectives, tNode.directiveEnd - tNode.directiveStart,
|
||||
'Reached the max number of directives');
|
||||
|
|
|
@ -434,6 +434,19 @@ export interface TNode {
|
|||
*/
|
||||
attrs: TAttributes|null;
|
||||
|
||||
/**
|
||||
* Same as `TNode.attrs` but contains merged data across all directive host bindings.
|
||||
*
|
||||
* We need to keep `attrs` as unmerged so that it can be used for attribute selectors.
|
||||
* We merge attrs here so that it can be used in a performant way for initial rendering.
|
||||
*
|
||||
* The `attrs` are merged in first pass in following order:
|
||||
* - Component's `hostAttrs`
|
||||
* - Directives' `hostAttrs`
|
||||
* - Template `TNode.attrs` associated with the current `TNode`.
|
||||
*/
|
||||
mergedAttrs: TAttributes|null;
|
||||
|
||||
/**
|
||||
* A set of local names under which a given element is exported in a template and
|
||||
* visible to queries. An entry in this array can be created for different reasons:
|
||||
|
|
|
@ -8,32 +8,52 @@
|
|||
|
||||
import '../util/ng_dev_mode';
|
||||
|
||||
import {assertDefined, assertNotEqual} from '../util/assert';
|
||||
import {assertDefined, assertEqual, assertNotEqual} from '../util/assert';
|
||||
|
||||
import {AttributeMarker, TAttributes, TNode, TNodeType, unusedValueExportToPlacateAjd as unused1} from './interfaces/node';
|
||||
import {CssSelector, CssSelectorList, SelectorFlags, unusedValueExportToPlacateAjd as unused2} from './interfaces/projection';
|
||||
import {classIndexOf} from './styling/class_differ';
|
||||
import {isNameOnlyAttributeMarker} from './util/attrs_utils';
|
||||
import {getInitialStylingValue} from './util/styling_utils';
|
||||
|
||||
const unusedValueToPlacateAjd = unused1 + unused2;
|
||||
|
||||
const NG_TEMPLATE_SELECTOR = 'ng-template';
|
||||
|
||||
function isCssClassMatching(nodeClassAttrVal: string, cssClassToMatch: string): boolean {
|
||||
const nodeClassesLen = nodeClassAttrVal.length;
|
||||
// we lowercase the class attribute value to be able to match
|
||||
// selectors without case-sensitivity
|
||||
// (selectors are already in lowercase when generated)
|
||||
const matchIndex = nodeClassAttrVal.toLowerCase().indexOf(cssClassToMatch);
|
||||
const matchEndIdx = matchIndex + cssClassToMatch.length;
|
||||
if (matchIndex === -1 // no match
|
||||
|| (matchIndex > 0 && nodeClassAttrVal ![matchIndex - 1] !== ' ') // no space before
|
||||
||
|
||||
(matchEndIdx < nodeClassesLen && nodeClassAttrVal ![matchEndIdx] !== ' ')) // no space after
|
||||
{
|
||||
return false;
|
||||
/**
|
||||
* Search the `TAttributes` to see if it contains `cssClassToMatch` (case insensitive)
|
||||
*
|
||||
* @param attrs `TAttributes` to search through.
|
||||
* @param cssClassToMatch class to match (lowercase)
|
||||
* @param isProjectionMode Whether or not class matching should look into the attribute `class` in
|
||||
* addition to the `AttributeMarker.Classes`.
|
||||
*/
|
||||
function isCssClassMatching(
|
||||
attrs: TAttributes, cssClassToMatch: string, isProjectionMode: boolean): boolean {
|
||||
// TODO(misko): The fact that this function needs to know about `isProjectionMode` seems suspect.
|
||||
// It is strange to me that sometimes the class information comes in form of `class` attribute
|
||||
// and sometimes in form of `AttributeMarker.Classes`. Some investigation is needed to determine
|
||||
// if that is the right behavior.
|
||||
ngDevMode &&
|
||||
assertEqual(
|
||||
cssClassToMatch, cssClassToMatch.toLowerCase(), 'Class name expected to be lowercase.');
|
||||
let i = 0;
|
||||
while (i < attrs.length) {
|
||||
let item = attrs[i++];
|
||||
if (isProjectionMode && item === 'class') {
|
||||
item = attrs[i] as string;
|
||||
if (classIndexOf(item.toLowerCase(), cssClassToMatch, 0) !== -1) {
|
||||
return true;
|
||||
}
|
||||
} else if (item === AttributeMarker.Classes) {
|
||||
// We found the classes section. Start searching for the class.
|
||||
while (i < attrs.length && typeof(item = attrs[i++]) == 'string') {
|
||||
// while we have strings
|
||||
if (item.toLowerCase() === cssClassToMatch) return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -106,9 +126,8 @@ export function isNodeMatchingSelector(
|
|||
|
||||
// special case for matching against classes when a tNode has been instantiated with
|
||||
// class and style values as separate attribute values (e.g. ['title', CLASS, 'foo'])
|
||||
if ((mode & SelectorFlags.CLASS) && tNode.classes) {
|
||||
if (!isCssClassMatching(
|
||||
getInitialStylingValue(tNode.classes), selectorAttrValue as string)) {
|
||||
if ((mode & SelectorFlags.CLASS) && tNode.attrs !== null) {
|
||||
if (!isCssClassMatching(tNode.attrs, selectorAttrValue as string, isProjectionMode)) {
|
||||
if (isPositive(mode)) return false;
|
||||
skipToNextSelector = true;
|
||||
}
|
||||
|
@ -143,7 +162,7 @@ export function isNodeMatchingSelector(
|
|||
|
||||
const compareAgainstClassName = mode & SelectorFlags.CLASS ? nodeAttrValue : null;
|
||||
if (compareAgainstClassName &&
|
||||
!isCssClassMatching(compareAgainstClassName, selectorAttrValue as string) ||
|
||||
classIndexOf(compareAgainstClassName, selectorAttrValue as string, 0) !== -1 ||
|
||||
mode & SelectorFlags.ATTRIBUTE && selectorAttrValue !== nodeAttrValue) {
|
||||
if (isPositive(mode)) return false;
|
||||
skipToNextSelector = true;
|
||||
|
|
|
@ -194,14 +194,6 @@ export function decreaseElementDepthCount() {
|
|||
instructionState.lFrame.elementDepthCount--;
|
||||
}
|
||||
|
||||
export function getCurrentDirectiveDef(): DirectiveDef<any>|ComponentDef<any>|null {
|
||||
return instructionState.lFrame.currentDirectiveDef;
|
||||
}
|
||||
|
||||
export function setCurrentDirectiveDef(def: DirectiveDef<any>| ComponentDef<any>| null): void {
|
||||
instructionState.lFrame.currentDirectiveDef = def;
|
||||
}
|
||||
|
||||
export function getBindingsEnabled(): boolean {
|
||||
return instructionState.bindingsEnabled;
|
||||
}
|
||||
|
|
|
@ -113,7 +113,7 @@ export function removeClass(className: string, classToRemove: string): string {
|
|||
*
|
||||
* @param className A string containing classes (whitespace separated)
|
||||
* @param classToToggle A class name to remove or add to the `className`
|
||||
* @param toggle Weather the resulting `className` should contain or not the `classToToggle`
|
||||
* @param toggle Whether the resulting `className` should contain or not the `classToToggle`
|
||||
* @returns a new class-list which does not have `classToRemove`
|
||||
*/
|
||||
export function toggleClass(className: string, classToToggle: string, toggle: boolean): string {
|
||||
|
|
|
@ -5,7 +5,6 @@
|
|||
* 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 {assertEqual} from '../../util/assert';
|
||||
import {CharCode} from '../../util/char_code';
|
||||
import {AttributeMarker, TAttributes} from '../interfaces/node';
|
||||
import {CssSelector} from '../interfaces/projection';
|
||||
|
|
|
@ -830,6 +830,30 @@ describe('host bindings', () => {
|
|||
expect(hostElement.title).toBe('other title');
|
||||
});
|
||||
|
||||
it('should merge attributes on host and template', () => {
|
||||
@Directive({selector: '[dir1]', host: {id: 'dir1'}})
|
||||
class MyDir1 {
|
||||
}
|
||||
@Directive({selector: '[dir2]', host: {id: 'dir2'}})
|
||||
class MyDir2 {
|
||||
}
|
||||
|
||||
@Component({template: `<div dir1 dir2 id="tmpl"></div>`})
|
||||
class MyComp {
|
||||
}
|
||||
|
||||
TestBed.configureTestingModule({declarations: [MyComp, MyDir1, MyDir2]});
|
||||
const fixture = TestBed.createComponent(MyComp);
|
||||
fixture.detectChanges();
|
||||
const div: HTMLElement = fixture.debugElement.nativeElement.firstChild;
|
||||
expect(div.id).toEqual(
|
||||
ivyEnabled ?
|
||||
// In ivy the correct result is `tmpl` because template has the highest priority.
|
||||
'tmpl' :
|
||||
// In VE the order was simply that of execution and so dir2 would win.
|
||||
'dir2');
|
||||
});
|
||||
|
||||
onlyInIvy('Host bindings do not get merged in ViewEngine')
|
||||
.it('should work correctly with inherited directives with hostBindings', () => {
|
||||
@Directive({selector: '[superDir]', host: {'[id]': 'id'}})
|
||||
|
|
|
@ -191,6 +191,9 @@
|
|||
{
|
||||
"name": "callHooks"
|
||||
},
|
||||
{
|
||||
"name": "classIndexOf"
|
||||
},
|
||||
{
|
||||
"name": "concatString"
|
||||
},
|
||||
|
@ -279,7 +282,7 @@
|
|||
"name": "findAttrIndexInNode"
|
||||
},
|
||||
{
|
||||
"name": "findDirectiveMatches"
|
||||
"name": "findDirectiveDefMatches"
|
||||
},
|
||||
{
|
||||
"name": "forceStylesAsString"
|
||||
|
@ -407,9 +410,6 @@
|
|||
{
|
||||
"name": "getStylingMapArray"
|
||||
},
|
||||
{
|
||||
"name": "getTNode"
|
||||
},
|
||||
{
|
||||
"name": "growHostVarsSpace"
|
||||
},
|
||||
|
@ -444,7 +444,7 @@
|
|||
"name": "incrementInitPhaseFlags"
|
||||
},
|
||||
{
|
||||
"name": "initNodeFlags"
|
||||
"name": "initTNodeFlags"
|
||||
},
|
||||
{
|
||||
"name": "initializeInputAndOutputAliases"
|
||||
|
@ -533,6 +533,12 @@
|
|||
{
|
||||
"name": "matchTemplateAttribute"
|
||||
},
|
||||
{
|
||||
"name": "mergeHostAttribute"
|
||||
},
|
||||
{
|
||||
"name": "mergeHostAttrs"
|
||||
},
|
||||
{
|
||||
"name": "nativeAppendChild"
|
||||
},
|
||||
|
@ -638,9 +644,6 @@
|
|||
{
|
||||
"name": "setClassName"
|
||||
},
|
||||
{
|
||||
"name": "setCurrentDirectiveDef"
|
||||
},
|
||||
{
|
||||
"name": "setCurrentQueryIndex"
|
||||
},
|
||||
|
@ -719,9 +722,6 @@
|
|||
{
|
||||
"name": "ɵɵelementEnd"
|
||||
},
|
||||
{
|
||||
"name": "ɵɵelementHostAttrs"
|
||||
},
|
||||
{
|
||||
"name": "ɵɵelementStart"
|
||||
},
|
||||
|
|
|
@ -347,9 +347,6 @@
|
|||
{
|
||||
"name": "getStylingMapArray"
|
||||
},
|
||||
{
|
||||
"name": "getTNode"
|
||||
},
|
||||
{
|
||||
"name": "growHostVarsSpace"
|
||||
},
|
||||
|
@ -372,7 +369,7 @@
|
|||
"name": "incrementInitPhaseFlags"
|
||||
},
|
||||
{
|
||||
"name": "initNodeFlags"
|
||||
"name": "initTNodeFlags"
|
||||
},
|
||||
{
|
||||
"name": "insertBloom"
|
||||
|
@ -518,9 +515,6 @@
|
|||
{
|
||||
"name": "setClassName"
|
||||
},
|
||||
{
|
||||
"name": "setCurrentDirectiveDef"
|
||||
},
|
||||
{
|
||||
"name": "setCurrentQueryIndex"
|
||||
},
|
||||
|
@ -572,9 +566,6 @@
|
|||
{
|
||||
"name": "ɵɵdefineComponent"
|
||||
},
|
||||
{
|
||||
"name": "ɵɵelementHostAttrs"
|
||||
},
|
||||
{
|
||||
"name": "ɵɵtext"
|
||||
}
|
||||
|
|
|
@ -437,6 +437,9 @@
|
|||
{
|
||||
"name": "checkNoChangesInternal"
|
||||
},
|
||||
{
|
||||
"name": "classIndexOf"
|
||||
},
|
||||
{
|
||||
"name": "cleanUpView"
|
||||
},
|
||||
|
@ -579,7 +582,7 @@
|
|||
"name": "findAttrIndexInNode"
|
||||
},
|
||||
{
|
||||
"name": "findDirectiveMatches"
|
||||
"name": "findDirectiveDefMatches"
|
||||
},
|
||||
{
|
||||
"name": "findExistingListener"
|
||||
|
@ -867,7 +870,7 @@
|
|||
"name": "incrementInitPhaseFlags"
|
||||
},
|
||||
{
|
||||
"name": "initNodeFlags"
|
||||
"name": "initTNodeFlags"
|
||||
},
|
||||
{
|
||||
"name": "initializeInputAndOutputAliases"
|
||||
|
@ -1040,6 +1043,12 @@
|
|||
{
|
||||
"name": "matchTemplateAttribute"
|
||||
},
|
||||
{
|
||||
"name": "mergeHostAttribute"
|
||||
},
|
||||
{
|
||||
"name": "mergeHostAttrs"
|
||||
},
|
||||
{
|
||||
"name": "nativeAppendChild"
|
||||
},
|
||||
|
@ -1217,9 +1226,6 @@
|
|||
{
|
||||
"name": "setClassName"
|
||||
},
|
||||
{
|
||||
"name": "setCurrentDirectiveDef"
|
||||
},
|
||||
{
|
||||
"name": "setCurrentQueryIndex"
|
||||
},
|
||||
|
@ -1382,9 +1388,6 @@
|
|||
{
|
||||
"name": "ɵɵelementEnd"
|
||||
},
|
||||
{
|
||||
"name": "ɵɵelementHostAttrs"
|
||||
},
|
||||
{
|
||||
"name": "ɵɵelementStart"
|
||||
},
|
||||
|
|
|
@ -22,7 +22,7 @@ describe('css selector matching', () => {
|
|||
const tNode = (!attrsOrTNode || Array.isArray(attrsOrTNode)) ?
|
||||
createTNode(null !, null, TNodeType.Element, 0, tagName, attrsOrTNode as TAttributes) :
|
||||
(attrsOrTNode as TNode);
|
||||
return isNodeMatchingSelector(tNode, selector, false);
|
||||
return isNodeMatchingSelector(tNode, selector, true);
|
||||
}
|
||||
|
||||
describe('isNodeMatchingSimpleSelector', () => {
|
||||
|
@ -322,26 +322,6 @@ describe('css selector matching', () => {
|
|||
// <div class="foo">
|
||||
expect(isMatching('div', ['class', 'foo'], selector)).toBeFalsy();
|
||||
});
|
||||
|
||||
it('should match against a class value before and after the styling context is created',
|
||||
() => {
|
||||
// selector: 'div.abc'
|
||||
const selector = ['div', SelectorFlags.CLASS, 'abc'];
|
||||
const tNode = createTNode(null !, null, TNodeType.Element, 0, 'div', []);
|
||||
|
||||
// <div> (without attrs or styling context)
|
||||
expect(isMatching('div', tNode, selector)).toBeFalsy();
|
||||
|
||||
// <div class="abc"> (with attrs but without styling context)
|
||||
tNode.attrs = ['class', 'abc'];
|
||||
tNode.classes = null;
|
||||
expect(isMatching('div', tNode, selector)).toBeTruthy();
|
||||
|
||||
// <div class="abc"> (with styling context but without attrs)
|
||||
tNode.classes = ['abc', 'abc', true];
|
||||
tNode.attrs = null;
|
||||
expect(isMatching('div', tNode, selector)).toBeTruthy();
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
|
|
Loading…
Reference in New Issue