Skip to content

Commit f03475c

Browse files
matskojasonaden
authored andcommitted
refactor(ivy): evaluate prop-based styling bindings with a new algorithm (angular#30469)
This is the first refactor PR designed to change how styling bindings (i.e. `[style]` and `[class]`) behave in Ivy. Instead of having a heavy element-by-element context be generated for each element, this new refactor aims to use a single context for each `tNode` element that is examined and iterated over when styling values are to be applied to the element. This patch brings this new functionality to prop-based bindings such as `[style.prop]` and `[class.name]`. PR Close angular#30469
1 parent 848e53e commit f03475c

24 files changed

+1993
-26
lines changed

integration/_payload-limits.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,7 @@
2121
"master": {
2222
"uncompressed": {
2323
"runtime": 1440,
24-
"main": 146225,
24+
"main": 147764,
2525
"polyfills": 43567
2626
}
2727
}

packages/compiler/src/render3/view/compiler.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,6 @@ import {BindingForm, convertPropertyBinding} from '../../compiler_util/expressio
1313
import {ConstantPool, DefinitionKind} from '../../constant_pool';
1414
import * as core from '../../core';
1515
import {AST, ParsedEvent, ParsedEventType, ParsedProperty} from '../../expression_parser/ast';
16-
import {LifecycleHooks} from '../../lifecycle_reflector';
1716
import {DEFAULT_INTERPOLATION_CONFIG} from '../../ml_parser/interpolation_config';
1817
import * as o from '../../output/output_ast';
1918
import {ParseError, ParseSourceSpan, typeSourceSpan} from '../../parse_util';
@@ -725,6 +724,7 @@ function createHostBindingsFunction(
725724
// the update block of a component/directive templateFn/hostBindingsFn so that the bindings
726725
// are evaluated and updated for the element.
727726
styleBuilder.buildUpdateLevelInstructions(getValueConverter()).forEach(instruction => {
727+
totalHostVarsCount += instruction.allocateBindingSlots;
728728
updateStatements.push(createStylingStmt(instruction, bindingContext, bindingFn));
729729
});
730730
}

packages/compiler/src/render3/view/styling_builder.ts

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@ import * as t from '../r3_ast';
1515
import {Identifiers as R3} from '../r3_identifiers';
1616

1717
import {parse as parseStyle} from './style_parser';
18+
import {compilerIsNewStylingInUse} from './styling_state';
1819
import {ValueConverter} from './template';
1920

2021
const IMPORTANT_FLAG = '!important';
@@ -389,6 +390,11 @@ export class StylingBuilder {
389390
const bindingIndex: number = mapIndex.get(input.name !) !;
390391
const value = input.value.visit(valueConverter);
391392
totalBindingSlotsRequired += (value instanceof Interpolation) ? value.expressions.length : 0;
393+
if (compilerIsNewStylingInUse()) {
394+
// the old implementation does not reserve slot values for
395+
// binding entries. The new one does.
396+
totalBindingSlotsRequired++;
397+
}
392398
return {
393399
sourceSpan: input.sourceSpan,
394400
allocateBindingSlots: totalBindingSlotsRequired, reference,
Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
1+
/**
2+
* @license
3+
* Copyright Google Inc. All Rights Reserved.
4+
*
5+
* Use of this source code is governed by an MIT-style license that can be
6+
* found in the LICENSE file at https://angular.io/license
7+
*/
8+
9+
/**
10+
* A temporary enum of states that inform the core whether or not
11+
* to defer all styling instruction calls to the old or new
12+
* styling implementation.
13+
*/
14+
export const enum CompilerStylingMode {
15+
UseOld = 0,
16+
UseBothOldAndNew = 1,
17+
UseNew = 2,
18+
}
19+
20+
let _stylingMode = 0;
21+
22+
/**
23+
* Temporary function used to inform the existing styling algorithm
24+
* code to delegate all styling instruction calls to the new refactored
25+
* styling code.
26+
*/
27+
export function compilerSetStylingMode(mode: CompilerStylingMode) {
28+
_stylingMode = mode;
29+
}
30+
31+
export function compilerIsNewStylingInUse() {
32+
return _stylingMode > CompilerStylingMode.UseOld;
33+
}
34+
35+
export function compilerAllowOldStyling() {
36+
return _stylingMode < CompilerStylingMode.UseNew;
37+
}

packages/core/src/render3/debug.ts

Lines changed: 18 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -15,11 +15,10 @@ import {LQueries} from './interfaces/query';
1515
import {RComment, RElement} from './interfaces/renderer';
1616
import {StylingContext} from './interfaces/styling';
1717
import {BINDING_INDEX, CHILD_HEAD, CHILD_TAIL, CLEANUP, CONTENT_QUERIES, CONTEXT, DECLARATION_VIEW, FLAGS, HEADER_OFFSET, HOST, INJECTOR, LView, LViewFlags, NEXT, PARENT, QUERIES, RENDERER, RENDERER_FACTORY, SANITIZER, TVIEW, T_HOST} from './interfaces/view';
18-
import {getTNode, unwrapRNode} from './util/view_utils';
19-
20-
function attachDebugObject(obj: any, debug: any) {
21-
Object.defineProperty(obj, 'debug', {value: debug, enumerable: false});
22-
}
18+
import {runtimeIsNewStylingInUse} from './styling_next/state';
19+
import {DebugStyling as DebugNewStyling, NodeStylingDebug} from './styling_next/styling_debug';
20+
import {attachDebugObject} from './util/debug_utils';
21+
import {getTNode, isStylingContext, unwrapRNode} from './util/view_utils';
2322

2423
/*
2524
* This file contains conditionally attached classes which provide human readable (debug) level
@@ -171,6 +170,8 @@ export class LViewDebug {
171170
export interface DebugNode {
172171
html: string|null;
173172
native: Node;
173+
styles: DebugNewStyling|null;
174+
classes: DebugNewStyling|null;
174175
nodes: DebugNode[]|null;
175176
component: LViewDebug|null;
176177
}
@@ -188,12 +189,21 @@ export function toDebugNodes(tNode: TNode | null, lView: LView): DebugNode[]|nul
188189
while (tNodeCursor) {
189190
const rawValue = lView[tNode.index];
190191
const native = unwrapRNode(rawValue);
191-
const componentLViewDebug = toDebug(readLViewValue(rawValue));
192+
const componentLViewDebug =
193+
isStylingContext(rawValue) ? null : toDebug(readLViewValue(rawValue));
194+
195+
let styles: DebugNewStyling|null = null;
196+
let classes: DebugNewStyling|null = null;
197+
if (runtimeIsNewStylingInUse()) {
198+
styles = tNode.newStyles ? new NodeStylingDebug(tNode.newStyles, lView) : null;
199+
classes = tNode.newClasses ? new NodeStylingDebug(tNode.newClasses, lView) : null;
200+
}
201+
192202
debugNodes.push({
193203
html: toHtml(native),
194-
native: native as any,
204+
native: native as any, styles, classes,
195205
nodes: toDebugNodes(tNode.child, lView),
196-
component: componentLViewDebug
206+
component: componentLViewDebug,
197207
});
198208
tNodeCursor = tNodeCursor.next;
199209
}

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

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,8 @@ import {applyOnCreateInstructions} from '../node_util';
2121
import {decreaseElementDepthCount, getElementDepthCount, getIsParent, getLView, getPreviousOrParentTNode, getSelectedIndex, increaseElementDepthCount, setIsParent, setPreviousOrParentTNode} from '../state';
2222
import {getInitialClassNameValue, getInitialStyleStringValue, initializeStaticContext, patchContextWithStaticAttrs, renderInitialClasses, renderInitialStyles} from '../styling/class_and_style_bindings';
2323
import {getStylingContextFromLView, hasClassInput, hasStyleInput} from '../styling/util';
24+
import {registerInitialStylingIntoContext} from '../styling_next/instructions';
25+
import {runtimeIsNewStylingInUse} from '../styling_next/state';
2426
import {NO_CHANGE} from '../tokens';
2527
import {attrsStylingIndexOf, setUpAttributes} from '../util/attrs_utils';
2628
import {renderStringify} from '../util/misc_utils';
@@ -63,8 +65,9 @@ export function ΔelementStart(
6365
let initialStylesIndex = 0;
6466
let initialClassesIndex = 0;
6567

68+
let lastAttrIndex = -1;
6669
if (attrs) {
67-
const lastAttrIndex = setUpAttributes(native, attrs);
70+
lastAttrIndex = setUpAttributes(native, attrs);
6871

6972
// it's important to only prepare styling-related datastructures once for a given
7073
// tNode and not each time an element is created. Also, the styling code is designed
@@ -116,6 +119,10 @@ export function ΔelementStart(
116119
renderInitialStyles(native, tNode.stylingTemplate, renderer, initialStylesIndex);
117120
}
118121

122+
if (runtimeIsNewStylingInUse() && lastAttrIndex >= 0) {
123+
registerInitialStylingIntoContext(tNode, attrs as TAttributes, lastAttrIndex);
124+
}
125+
119126
const currentQueries = lView[QUERIES];
120127
if (currentQueries) {
121128
currentQueries.addNode(tNode);

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

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -777,6 +777,10 @@ export function createTNode(
777777
stylingTemplate: null,
778778
projection: null,
779779
onElementCreationFns: null,
780+
// TODO (matsko): rename this to `styles` once the old styling impl is gone
781+
newStyles: null,
782+
// TODO (matsko): rename this to `classes` once the old styling impl is gone
783+
newClasses: null,
780784
};
781785
}
782786

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

Lines changed: 43 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,9 @@ import {BoundPlayerFactory} from '../styling/player_factory';
1717
import {DEFAULT_TEMPLATE_DIRECTIVE_INDEX} from '../styling/shared';
1818
import {getCachedStylingContext, setCachedStylingContext} from '../styling/state';
1919
import {allocateOrUpdateDirectiveIntoContext, createEmptyStylingContext, forceClassesAsString, forceStylesAsString, getStylingContextFromLView, hasClassInput, hasStyleInput} from '../styling/util';
20+
import {classProp as newClassProp, styleProp as newStyleProp, stylingApply as newStylingApply, stylingInit as newStylingInit} from '../styling_next/instructions';
21+
import {runtimeAllowOldStyling, runtimeIsNewStylingInUse} from '../styling_next/state';
22+
import {getBindingNameFromIndex} from '../styling_next/util';
2023
import {NO_CHANGE} from '../tokens';
2124
import {renderStringify} from '../util/misc_utils';
2225
import {getRootContext} from '../util/view_traversal_utils';
@@ -73,6 +76,13 @@ export function Δstyling(
7376

7477
const directiveStylingIndex = getActiveDirectiveStylingIndex();
7578
if (directiveStylingIndex) {
79+
// this is temporary hack to get the existing styling instructions to
80+
// play ball with the new refactored implementation.
81+
// TODO (matsko): remove this once the old implementation is not needed.
82+
if (runtimeIsNewStylingInUse()) {
83+
newStylingInit();
84+
}
85+
7686
// despite the binding being applied in a queue (below), the allocation
7787
// of the directive into the context happens right away. The reason for
7888
// this is to retain the ordering of the directives (which is important
@@ -81,7 +91,7 @@ export function Δstyling(
8191

8292
const fns = tNode.onElementCreationFns = tNode.onElementCreationFns || [];
8393
fns.push(() => {
84-
initstyling(
94+
initStyling(
8595
tNode, classBindingNames, styleBindingNames, styleSanitizer, directiveStylingIndex);
8696
registerHostDirective(tNode.stylingTemplate !, directiveStylingIndex);
8797
});
@@ -92,13 +102,13 @@ export function Δstyling(
92102
// components) then they will be applied at the end of the `elementEnd`
93103
// instruction (because directives are created first before styling is
94104
// executed for a new element).
95-
initstyling(
105+
initStyling(
96106
tNode, classBindingNames, styleBindingNames, styleSanitizer,
97107
DEFAULT_TEMPLATE_DIRECTIVE_INDEX);
98108
}
99109
}
100110

101-
function initstyling(
111+
function initStyling(
102112
tNode: TNode, classBindingNames: string[] | null | undefined,
103113
styleBindingNames: string[] | null | undefined,
104114
styleSanitizer: StyleSanitizeFn | null | undefined, directiveStylingIndex: number): void {
@@ -148,6 +158,15 @@ export function ΔstyleProp(
148158
updatestyleProp(
149159
stylingContext, styleIndex, valueToAdd, DEFAULT_TEMPLATE_DIRECTIVE_INDEX, forceOverride);
150160
}
161+
162+
if (runtimeIsNewStylingInUse()) {
163+
const prop = getBindingNameFromIndex(stylingContext, styleIndex, directiveStylingIndex, false);
164+
165+
// the reason why we cast the value as `boolean` is
166+
// because the new styling refactor does not yet support
167+
// sanitization or animation players.
168+
newStyleProp(prop, value as string | number, suffix);
169+
}
151170
}
152171

153172
function resolveStylePropValue(
@@ -206,6 +225,15 @@ export function ΔclassProp(
206225
updateclassProp(
207226
stylingContext, classIndex, input, DEFAULT_TEMPLATE_DIRECTIVE_INDEX, forceOverride);
208227
}
228+
229+
if (runtimeIsNewStylingInUse()) {
230+
const prop = getBindingNameFromIndex(stylingContext, classIndex, directiveStylingIndex, true);
231+
232+
// the reason why we cast the value as `boolean` is
233+
// because the new styling refactor does not yet support
234+
// sanitization or animation players.
235+
newClassProp(prop, input as boolean);
236+
}
209237
}
210238

211239

@@ -324,11 +352,14 @@ export function ΔstylingApply(): void {
324352
const renderer = tNode.type === TNodeType.Element ? lView[RENDERER] : null;
325353
const isFirstRender = (lView[FLAGS] & LViewFlags.FirstLViewPass) !== 0;
326354
const stylingContext = getStylingContext(index, lView);
327-
const totalPlayersQueued = renderStyling(
328-
stylingContext, renderer, lView, isFirstRender, null, null, directiveStylingIndex);
329-
if (totalPlayersQueued > 0) {
330-
const rootContext = getRootContext(lView);
331-
scheduleTick(rootContext, RootContextFlags.FlushPlayers);
355+
356+
if (runtimeAllowOldStyling()) {
357+
const totalPlayersQueued = renderStyling(
358+
stylingContext, renderer, lView, isFirstRender, null, null, directiveStylingIndex);
359+
if (totalPlayersQueued > 0) {
360+
const rootContext = getRootContext(lView);
361+
scheduleTick(rootContext, RootContextFlags.FlushPlayers);
362+
}
332363
}
333364

334365
// because select(n) may not run between every instruction, the cached styling
@@ -339,6 +370,10 @@ export function ΔstylingApply(): void {
339370
// cleared because there is no code in Angular that applies more styling code after a
340371
// styling flush has occurred. Note that this will be fixed once FW-1254 lands.
341372
setCachedStylingContext(null);
373+
374+
if (runtimeIsNewStylingInUse()) {
375+
newStylingApply();
376+
}
342377
}
343378

344379
export function getActiveDirectiveStylingIndex() {

packages/core/src/render3/interfaces/node.ts

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@
55
* Use of this source code is governed by an MIT-style license that can be
66
* found in the LICENSE file at https://angular.io/license
77
*/
8-
8+
import {TStylingContext} from '../styling_next/interfaces';
99
import {CssSelector} from './projection';
1010
import {RNode} from './renderer';
1111
import {StylingContext} from './styling';
@@ -438,6 +438,10 @@ export interface TNode {
438438
* with functions each time the creation block is called.
439439
*/
440440
onElementCreationFns: Function[]|null;
441+
// TODO (matsko): rename this to `styles` once the old styling impl is gone
442+
newStyles: TStylingContext|null;
443+
// TODO (matsko): rename this to `classes` once the old styling impl is gone
444+
newClasses: TStylingContext|null;
441445
}
442446

443447
/** Static data for an element */

packages/core/src/render3/jit/environment.ts

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -6,10 +6,11 @@
66
* found in the LICENSE file at https://angular.io/license
77
*/
88

9-
import {ΔdefineInjectable, ΔdefineInjector,} from '../../di/interface/defs';
109
import {Δinject} from '../../di/injector_compatibility';
11-
import * as r3 from '../index';
10+
import {ΔdefineInjectable, ΔdefineInjector} from '../../di/interface/defs';
1211
import * as sanitization from '../../sanitization/sanitization';
12+
import * as r3 from '../index';
13+
1314

1415

1516
/**

packages/core/src/render3/state.ts

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -245,6 +245,27 @@ export function adjustActiveDirectiveSuperClassDepthPosition(delta: number) {
245245
Math.max(activeDirectiveSuperClassHeight, activeDirectiveSuperClassDepthPosition);
246246
}
247247

248+
/**
249+
* Returns he current depth of the super/sub class inheritance chain.
250+
*
251+
* This will return how many inherited directive/component classes
252+
* exist in the current chain.
253+
*
254+
* ```typescript
255+
* @Directive({ selector: '[super-dir]' })
256+
* class SuperDir {}
257+
*
258+
* @Directive({ selector: '[sub-dir]' })
259+
* class SubDir extends SuperDir {}
260+
*
261+
* // if `<div sub-dir>` is used then the super class height is `1`
262+
* // if `<div super-dir>` is used then the super class height is `0`
263+
* ```
264+
*/
265+
export function getActiveDirectiveSuperClassHeight() {
266+
return activeDirectiveSuperClassHeight;
267+
}
268+
248269
/**
249270
* Returns the current super class (reverse inheritance) depth for a directive.
250271
*

packages/core/src/render3/styling/class_and_style_bindings.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1636,7 +1636,7 @@ function diffSummaryValues(result: any[], name: string, prop: string, a: any, b:
16361636
}
16371637
}
16381638

1639-
function getSinglePropIndexValue(
1639+
export function getSinglePropIndexValue(
16401640
context: StylingContext, directiveIndex: number, offset: number, isClassBased: boolean) {
16411641
const singlePropOffsetRegistryIndex =
16421642
context[StylingIndex.DirectiveRegistryPosition]

0 commit comments

Comments
 (0)