Skip to content

Commit ec56354

Browse files
matskoIgorMinar
authored andcommitted
fix(ivy): ensure parent/sub-class components evaluate styling correctly (angular#29602)
The new styling algorithm in angular is designed to evaluate host bindings stylinh priority in order of directive evaluation order. This, however, does not work with respect to parent/sub-class directives because sub-class host bindings are run after the parent host bindings but still have priority. This patch ensures that the host styling bindings for parent and sub-class components/directives are executed with respect to the styling algorithm prioritization. Jira Issue: FW-1132 PR Close angular#29602
1 parent 5c13fee commit ec56354

File tree

19 files changed

+1406
-915
lines changed

19 files changed

+1406
-915
lines changed

integration/_payload-limits.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@
1212
"master": {
1313
"uncompressed": {
1414
"runtime": 1440,
15-
"main": 14106,
15+
"main": 14287,
1616
"polyfills": 43567
1717
}
1818
}

packages/core/src/render3/component.ts

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,7 @@ import {PlayerHandler} from './interfaces/player';
2323
import {RElement, Renderer3, RendererFactory3, domRendererFactory3} from './interfaces/renderer';
2424
import {CONTEXT, FLAGS, HEADER_OFFSET, HOST, LView, LViewFlags, RENDERER, RootContext, RootContextFlags, TVIEW} from './interfaces/view';
2525
import {applyOnCreateInstructions} from './node_util';
26-
import {enterView, getPreviousOrParentTNode, leaveView, resetComponentState} from './state';
26+
import {enterView, getPreviousOrParentTNode, leaveView, resetComponentState, setActiveHostElement} from './state';
2727
import {renderInitialClasses, renderInitialStyles} from './styling/class_and_style_bindings';
2828
import {publishDefaultGlobalUtils} from './util/global_utils';
2929
import {defaultScheduler, renderStringify} from './util/misc_utils';
@@ -210,10 +210,15 @@ export function createRootComponent<T>(
210210

211211
const rootTNode = getPreviousOrParentTNode();
212212
if (tView.firstTemplatePass && componentDef.hostBindings) {
213+
const elementIndex = rootTNode.index - HEADER_OFFSET;
214+
setActiveHostElement(elementIndex);
215+
213216
const expando = tView.expandoInstructions !;
214217
invokeHostBindingsInCreationMode(
215218
componentDef, expando, component, rootTNode, tView.firstTemplatePass);
216219
rootTNode.onElementCreationFns && applyOnCreateInstructions(rootTNode);
220+
221+
setActiveHostElement(null);
217222
}
218223

219224
if (rootTNode.stylingTemplate) {

packages/core/src/render3/features/inherit_definition_feature.ts

Lines changed: 18 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ import {Type} from '../../interface/type';
1010
import {fillProperties} from '../../util/property';
1111
import {EMPTY_ARRAY, EMPTY_OBJ} from '../empty';
1212
import {ComponentDef, DirectiveDef, DirectiveDefFeature, RenderFlags} from '../interfaces/definition';
13+
import {adjustActiveDirectiveSuperClassDepthPosition} from '../state';
1314
import {isComponentDef} from '../util/view_utils';
1415

1516
import {NgOnChangesFeature} from './ng_onchanges_feature';
@@ -63,8 +64,24 @@ export function InheritDefinitionFeature(definition: DirectiveDef<any>| Componen
6364
const superHostBindings = superDef.hostBindings;
6465
if (superHostBindings) {
6566
if (prevHostBindings) {
67+
// because inheritance is unknown during compile time, the runtime code
68+
// needs to be informed of the super-class depth so that instruction code
69+
// can distinguish one host bindings function from another. The reason why
70+
// relying on the directive uniqueId exclusively is not enough is because the
71+
// uniqueId value and the directive instance stay the same between hostBindings
72+
// calls throughout the directive inheritance chain. This means that without
73+
// a super-class depth value, there is no way to know whether a parent or
74+
// sub-class host bindings function is currently being executed.
6675
definition.hostBindings = (rf: RenderFlags, ctx: any, elementIndex: number) => {
67-
superHostBindings(rf, ctx, elementIndex);
76+
// The reason why we increment first and then decrement is so that parent
77+
// hostBindings calls have a higher id value compared to sub-class hostBindings
78+
// calls (this way the leaf directive is always at a super-class depth of 0).
79+
adjustActiveDirectiveSuperClassDepthPosition(1);
80+
try {
81+
superHostBindings(rf, ctx, elementIndex);
82+
} finally {
83+
adjustActiveDirectiveSuperClassDepthPosition(-1);
84+
}
6885
prevHostBindings(rf, ctx, elementIndex);
6986
};
7087
} else {

packages/core/src/render3/instructions/element.ts

Lines changed: 24 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -11,20 +11,23 @@ import {assertHasParent} from '../assert';
1111
import {attachPatchData} from '../context_discovery';
1212
import {registerPostOrderHooks} from '../hooks';
1313
import {TAttributes, TNodeFlags, TNodeType} from '../interfaces/node';
14-
import {RElement, Renderer3, isProceduralRenderer} from '../interfaces/renderer';
14+
import {RElement, isProceduralRenderer} from '../interfaces/renderer';
1515
import {SanitizerFn} from '../interfaces/sanitization';
1616
import {BINDING_INDEX, QUERIES, RENDERER, TVIEW} from '../interfaces/view';
1717
import {assertNodeType} from '../node_assert';
1818
import {appendChild} from '../node_manipulation';
1919
import {applyOnCreateInstructions} from '../node_util';
20-
import {decreaseElementDepthCount, getActiveHostContext, getElementDepthCount, getIsParent, getLView, getNamespace, getPreviousOrParentTNode, increaseElementDepthCount, setIsParent, setPreviousOrParentTNode} from '../state';
20+
import {decreaseElementDepthCount, getActiveDirectiveId, getElementDepthCount, getIsParent, getLView, getNamespace, getPreviousOrParentTNode, getSelectedIndex, increaseElementDepthCount, setIsParent, setPreviousOrParentTNode} from '../state';
2121
import {getInitialClassNameValue, getInitialStyleStringValue, initializeStaticContext, patchContextWithStaticAttrs, renderInitialClasses, renderInitialStyles} from '../styling/class_and_style_bindings';
2222
import {getStylingContext, hasClassInput, hasStyleInput} from '../styling/util';
2323
import {NO_CHANGE} from '../tokens';
2424
import {attrsStylingIndexOf, setUpAttributes} from '../util/attrs_utils';
2525
import {renderStringify} from '../util/misc_utils';
2626
import {getNativeByIndex, getNativeByTNode, getTNode} from '../util/view_utils';
27+
2728
import {createDirectivesAndLocals, createNodeAtIndex, elementCreate, executeContentQueries, initializeTNodeInputs, setInputsForProperty, setNodeStylingTemplate} from './shared';
29+
import {getActiveDirectiveStylingIndex} from './styling_instructions';
30+
2831

2932
/**
3033
* Create DOM element. The instruction must later be followed by `elementEnd()` call.
@@ -256,17 +259,26 @@ export function elementAttribute(
256259
* @publicApi
257260
*/
258261
export function elementHostAttrs(attrs: TAttributes) {
259-
const tNode = getPreviousOrParentTNode();
262+
const hostElementIndex = getSelectedIndex();
260263
const lView = getLView();
261-
const native = getNativeByTNode(tNode, lView) as RElement;
262-
const lastAttrIndex = setUpAttributes(native, attrs);
263-
const stylingAttrsStartIndex = attrsStylingIndexOf(attrs, lastAttrIndex);
264-
if (stylingAttrsStartIndex >= 0) {
265-
const directive = getActiveHostContext();
266-
if (tNode.stylingTemplate) {
267-
patchContextWithStaticAttrs(tNode.stylingTemplate, attrs, stylingAttrsStartIndex, directive);
268-
} else {
269-
tNode.stylingTemplate = initializeStaticContext(attrs, stylingAttrsStartIndex, directive);
264+
const tNode = getTNode(hostElementIndex, lView);
265+
266+
// non-element nodes (e.g. `<ng-container>`) are not rendered as actual
267+
// element nodes and adding styles/classes on to them will cause runtime
268+
// errors...
269+
if (tNode.type === TNodeType.Element) {
270+
const native = getNativeByTNode(tNode, lView) as RElement;
271+
const lastAttrIndex = setUpAttributes(native, attrs);
272+
const stylingAttrsStartIndex = attrsStylingIndexOf(attrs, lastAttrIndex);
273+
if (stylingAttrsStartIndex >= 0) {
274+
const directiveStylingIndex = getActiveDirectiveStylingIndex();
275+
if (tNode.stylingTemplate) {
276+
patchContextWithStaticAttrs(
277+
tNode.stylingTemplate, attrs, stylingAttrsStartIndex, directiveStylingIndex);
278+
} else {
279+
tNode.stylingTemplate =
280+
initializeStaticContext(attrs, stylingAttrsStartIndex, directiveStylingIndex);
281+
}
270282
}
271283
}
272284
}

packages/core/src/render3/instructions/element_container.ts

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@ import {TAttributes, TNodeType} from '../interfaces/node';
1313
import {BINDING_INDEX, QUERIES, RENDERER, TVIEW} from '../interfaces/view';
1414
import {assertNodeType} from '../node_assert';
1515
import {appendChild} from '../node_manipulation';
16+
import {applyOnCreateInstructions} from '../node_util';
1617
import {getIsParent, getLView, getPreviousOrParentTNode, setIsParent, setPreviousOrParentTNode} from '../state';
1718
import {createDirectivesAndLocals, createNodeAtIndex, executeContentQueries, setNodeStylingTemplate} from './shared';
1819

@@ -83,5 +84,9 @@ export function elementContainerEnd(): void {
8384
lView[QUERIES] = currentQueries.parent;
8485
}
8586

87+
// this is required for all host-level styling-related instructions to run
88+
// in the correct order
89+
previousOrParentTNode.onElementCreationFns && applyOnCreateInstructions(previousOrParentTNode);
90+
8691
registerPostOrderHooks(tView, previousOrParentTNode);
8792
}

packages/core/src/render3/instructions/shared.ts

Lines changed: 24 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -27,14 +27,15 @@ import {StylingContext} from '../interfaces/styling';
2727
import {BINDING_INDEX, CHILD_HEAD, CHILD_TAIL, CLEANUP, CONTEXT, DECLARATION_VIEW, ExpandoInstructions, FLAGS, HEADER_OFFSET, HOST, INJECTOR, InitPhaseState, LView, LViewFlags, NEXT, PARENT, QUERIES, RENDERER, RENDERER_FACTORY, RootContext, RootContextFlags, SANITIZER, TVIEW, TView, T_HOST} from '../interfaces/view';
2828
import {assertNodeOfPossibleTypes, assertNodeType} from '../node_assert';
2929
import {isNodeMatchingSelectorList} from '../node_selector_matcher';
30-
import {enterView, getBindingsEnabled, getCheckNoChangesMode, getIsParent, getLView, getNamespace, getPreviousOrParentTNode, isCreationMode, leaveView, namespaceHTML, resetComponentState, setActiveHost, setBindingRoot, setCheckNoChangesMode, setCurrentDirectiveDef, setCurrentQueryIndex, setIsParent, setPreviousOrParentTNode, setSelectedIndex} from '../state';
30+
import {enterView, getBindingsEnabled, getCheckNoChangesMode, getIsParent, getLView, getNamespace, getPreviousOrParentTNode, incrementActiveDirectiveId, isCreationMode, leaveView, namespaceHTML, resetComponentState, setActiveHostElement, setBindingRoot, setCheckNoChangesMode, setCurrentDirectiveDef, setCurrentQueryIndex, setIsParent, setPreviousOrParentTNode, setSelectedIndex} from '../state';
3131
import {initializeStaticContext as initializeStaticStylingContext} from '../styling/class_and_style_bindings';
3232
import {NO_CHANGE} from '../tokens';
3333
import {attrsStylingIndexOf} from '../util/attrs_utils';
3434
import {INTERPOLATION_DELIMITER, renderStringify} from '../util/misc_utils';
3535
import {getLViewParent, getRootContext} from '../util/view_traversal_utils';
3636
import {getComponentViewByIndex, getNativeByTNode, isComponentDef, isContentQueryHost, isRootView, readPatchedLView, resetPreOrderHookFlags, unwrapRNode, viewAttachedToChangeDetector} from '../util/view_utils';
3737

38+
3839
/**
3940
* A permanent marker promise which signifies that the current CD tree is
4041
* clean.
@@ -107,6 +108,8 @@ export function setHostBindings(tView: TView, viewData: LView): void {
107108
// Negative numbers mean that we are starting new EXPANDO block and need to update
108109
// the current element and directive index.
109110
currentElementIndex = -instruction;
111+
setActiveHostElement(currentElementIndex);
112+
110113
// Injector block and providers are taken into account.
111114
const providerCount = (tView.expandoInstructions[++i] as number);
112115
bindingRootIndex += INJECTOR_BLOOM_PARENT_SIZE + providerCount;
@@ -124,14 +127,20 @@ export function setHostBindings(tView: TView, viewData: LView): void {
124127
if (instruction !== null) {
125128
viewData[BINDING_INDEX] = bindingRootIndex;
126129
const hostCtx = unwrapRNode(viewData[currentDirectiveIndex]);
127-
setActiveHost(hostCtx, currentElementIndex);
128130
instruction(RenderFlags.Update, hostCtx, currentElementIndex);
129-
setActiveHost(null);
131+
132+
// Each directive gets a uniqueId value that is the same for both
133+
// create and update calls when the hostBindings function is called. The
134+
// directive uniqueId is not set anywhere--it is just incremented between
135+
// each hostBindings call and is useful for helping instruction code
136+
// uniquely determine which directive is currently active when executed.
137+
incrementActiveDirectiveId();
130138
}
131139
currentDirectiveIndex++;
132140
}
133141
}
134142
}
143+
setActiveHostElement(null);
135144
}
136145

137146
/** Refreshes content queries for all directives in the given view. */
@@ -897,15 +906,27 @@ function invokeDirectivesHostBindings(tView: TView, viewData: LView, tNode: TNod
897906
const end = tNode.directiveEnd;
898907
const expando = tView.expandoInstructions !;
899908
const firstTemplatePass = tView.firstTemplatePass;
909+
const elementIndex = tNode.index - HEADER_OFFSET;
910+
setActiveHostElement(elementIndex);
911+
900912
for (let i = start; i < end; i++) {
901913
const def = tView.data[i] as DirectiveDef<any>;
902914
const directive = viewData[i];
903915
if (def.hostBindings) {
904916
invokeHostBindingsInCreationMode(def, expando, directive, tNode, firstTemplatePass);
917+
918+
// Each directive gets a uniqueId value that is the same for both
919+
// create and update calls when the hostBindings function is called. The
920+
// directive uniqueId is not set anywhere--it is just incremented between
921+
// each hostBindings call and is useful for helping instruction code
922+
// uniquely determine which directive is currently active when executed.
923+
incrementActiveDirectiveId();
905924
} else if (firstTemplatePass) {
906925
expando.push(null);
907926
}
908927
}
928+
929+
setActiveHostElement(null);
909930
}
910931

911932
export function invokeHostBindingsInCreationMode(
@@ -914,9 +935,7 @@ export function invokeHostBindingsInCreationMode(
914935
const previousExpandoLength = expando.length;
915936
setCurrentDirectiveDef(def);
916937
const elementIndex = tNode.index - HEADER_OFFSET;
917-
setActiveHost(directive, elementIndex);
918938
def.hostBindings !(RenderFlags.Create, directive, elementIndex);
919-
setActiveHost(null);
920939
setCurrentDirectiveDef(null);
921940
// `hostBindings` function may or may not contain `allocHostVars` call
922941
// (e.g. it may not if it only contains host listeners), so we need to check whether

0 commit comments

Comments
 (0)