Skip to content

Commit 5e6d857

Browse files
crisbetothePunderWoman
authored andcommitted
perf(core): avoid repeat searches for field directive
The `getControlDirective` is called multiple times, both at init and during each update run. Under the hood it performs a linear search for the `Field` directive. We can speed this up by finding its index once and reusing it since the array of directive matches is static.
1 parent 81ce1ba commit 5e6d857

File tree

4 files changed

+47
-52
lines changed

4 files changed

+47
-52
lines changed

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

Lines changed: 27 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@
77
*/
88
import {RuntimeError, RuntimeErrorCode} from '../../errors';
99
import {getClosureSafeProperty} from '../../util/property';
10+
import {assertFirstCreatePass} from '../assert';
1011
import {bindingUpdated} from '../bindings';
1112
import {ɵCONTROL, ɵControl, ɵFieldState} from '../interfaces/control';
1213
import {ComponentDef} from '../interfaces/definition';
@@ -47,9 +48,12 @@ export function ɵɵcontrolCreate(): void {
4748
const lView = getLView<{} | null>();
4849
const tView = getTView();
4950
const tNode = getCurrentTNode()!;
50-
const control = tView.firstCreatePass
51-
? getControlDirectiveFirstCreatePass(tView, tNode, lView)
52-
: getControlDirective(tNode, lView);
51+
52+
if (tView.firstCreatePass) {
53+
initializeControlFirstCreatePass(tView, tNode, lView);
54+
}
55+
56+
const control = getControlDirective(tNode, lView);
5357

5458
if (!control) {
5559
return;
@@ -114,11 +118,9 @@ export function ɵɵcontrol<T>(value: T, sanitizer?: SanitizerFn | null): void {
114118
const HAS_CONTROL_MASK = /* @__PURE__ */ (() =>
115119
TNodeFlags.isNativeControl | TNodeFlags.isFormValueControl | TNodeFlags.isFormCheckboxControl)();
116120

117-
function getControlDirectiveFirstCreatePass<T>(
118-
tView: TView,
119-
tNode: TNode,
120-
lView: LView,
121-
): ɵControl<T> | undefined {
121+
function initializeControlFirstCreatePass<T>(tView: TView, tNode: TNode, lView: LView): void {
122+
ngDevMode && assertFirstCreatePass(tView);
123+
122124
const directiveIndices = tNode.inputs?.['field'];
123125
if (!directiveIndices) {
124126
// There are no matching inputs for the `[field]` property binding.
@@ -137,13 +139,22 @@ function getControlDirectiveFirstCreatePass<T>(
137139
}
138140

139141
// Search for the `ɵControl` directive.
140-
const control = findControlDirective<T>(lView, directiveIndices);
141-
if (!control) {
142+
let controlIndex = -1;
143+
144+
for (let index of directiveIndices) {
145+
if (ɵCONTROL in lView[index]) {
146+
controlIndex = index;
147+
break;
148+
}
149+
}
150+
151+
if (controlIndex === -1) {
142152
// The `ɵControl` directive was not imported by this component.
143153
return;
144154
}
145155

146-
tNode.flags |= TNodeFlags.isFormControl;
156+
const control = lView[controlIndex] as ɵControl<T>;
157+
tNode.fieldIndex = controlIndex;
147158

148159
if (isComponentHost(tNode)) {
149160
const componentDef = tView.data[componentIndex] as ComponentDef<unknown>;
@@ -158,7 +169,7 @@ function getControlDirectiveFirstCreatePass<T>(
158169
// Only check for an interop control if we haven't already found a custom one.
159170
if (!(tNode.flags & HAS_CONTROL_MASK) && control.ɵinteropControl) {
160171
tNode.flags |= TNodeFlags.isInteropControl;
161-
return control;
172+
return;
162173
}
163174

164175
if (isNativeControl(tNode)) {
@@ -172,7 +183,7 @@ function getControlDirectiveFirstCreatePass<T>(
172183
}
173184

174185
if (tNode.flags & HAS_CONTROL_MASK) {
175-
return control;
186+
return;
176187
}
177188

178189
const tagName = tNode.value;
@@ -193,25 +204,9 @@ function getControlDirectiveFirstCreatePass<T>(
193204
* @param tNode The `TNode` of the element to check.
194205
* @param lView The `LView` that contains the element.
195206
*/
196-
function getControlDirective<T>(tNode: TNode, lView: LView): ɵControl<T> | undefined {
197-
return tNode.flags & TNodeFlags.isFormControl
198-
? findControlDirective(lView, tNode.inputs!['field'])
199-
: undefined;
200-
}
201-
202-
function findControlDirective<T>(
203-
lView: LView,
204-
directiveIndices: number[],
205-
): ɵControl<T> | undefined {
206-
for (let index of directiveIndices) {
207-
const directive = lView[index];
208-
if (ɵCONTROL in directive) {
209-
return directive;
210-
}
211-
}
212-
213-
// The `Field` directive was not imported by this component.
214-
return;
207+
function getControlDirective<T>(tNode: TNode, lView: LView): ɵControl<T> | null {
208+
const index = tNode.fieldIndex;
209+
return index === -1 ? null : lView[index];
215210
}
216211

217212
/** Returns whether the specified `componentDef` has a model input named `name`. */

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

Lines changed: 18 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -172,55 +172,47 @@ export const enum TNodeFlags {
172172
isInControlFlow = 1 << 9,
173173

174174
/**
175-
* Bit #11 - This bit is set if the node represents a form control.
176-
*
177-
* True when the node has an input binding to a `ɵControl` directive (but not also to a custom
178-
* component).
179-
*/
180-
isFormControl = 1 << 10,
181-
182-
/**
183-
* Bit #12 - This bit is set if the node hosts a custom control component.
175+
* Bit #11 - This bit is set if the node hosts a custom control component.
184176
*
185177
* A custom control component's model property is named `value`.
186178
*/
187-
isFormValueControl = 1 << 11,
179+
isFormValueControl = 1 << 10,
188180

189181
/**
190-
* Bit #13 - This bit is set if the node hosts a custom checkbox component.
182+
* Bit #12 - This bit is set if the node hosts a custom checkbox component.
191183
*
192184
* A custom checkbox component's model property is named `checked`.
193185
*/
194-
isFormCheckboxControl = 1 << 12,
186+
isFormCheckboxControl = 1 << 11,
195187

196188
/**
197-
* Bit #14 - This bit is set if the node hosts an interoperable control implementation.
189+
* Bit #13 - This bit is set if the node hosts an interoperable control implementation.
198190
*
199191
* This is used to bind to a `ControlValueAccessor` from `@angular/forms`.
200192
*/
201-
isInteropControl = 1 << 13,
193+
isInteropControl = 1 << 12,
202194

203195
/**
204-
* Bit #15 - This bit is set if the node is a native control.
196+
* Bit #14 - This bit is set if the node is a native control.
205197
*
206198
* This is used to determine whether we can bind common control properties to the host element of
207199
* a custom control when it doesn't define a corresponding input.
208200
*/
209-
isNativeControl = 1 << 14,
201+
isNativeControl = 1 << 13,
210202

211203
/**
212-
* Bit #16 - This bit is set if the node is a native control with a numeric type.
204+
* Bit #15 - This bit is set if the node is a native control with a numeric type.
213205
*
214206
* This is used to determine whether the control supports the `min` and `max` properties.
215207
*/
216-
isNativeNumericControl = 1 << 15,
208+
isNativeNumericControl = 1 << 14,
217209

218210
/**
219-
* Bit #17 - This bit is set if the node is a native text control.
211+
* Bit #16 - This bit is set if the node is a native text control.
220212
*
221213
* This is used to determine whether control supports the `minLength` and `maxLength` properties.
222214
*/
223-
isNativeTextControl = 1 << 16,
215+
isNativeTextControl = 1 << 15,
224216
}
225217

226218
/**
@@ -381,6 +373,12 @@ export interface TNode {
381373
*/
382374
componentOffset: number;
383375

376+
/**
377+
* Index at which the signal forms field directive is stored.
378+
* Value is set to -1 if there are no field directives.
379+
*/
380+
fieldIndex: number;
381+
384382
/**
385383
* Stores the last directive which had a styling instruction.
386384
*

packages/core/src/render3/tnode_manipulation.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -298,6 +298,7 @@ export function createTNode(
298298
directiveEnd: -1,
299299
directiveStylingLast: -1,
300300
componentOffset: -1,
301+
fieldIndex: -1,
301302
propertyBindings: null,
302303
flags,
303304
providerIndexes: 0,

packages/core/test/render3/is_shape_of.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -154,6 +154,7 @@ const ShapeOfTNode: ShapeOf<TNode> = {
154154
directiveEnd: true,
155155
directiveStylingLast: true,
156156
componentOffset: true,
157+
fieldIndex: true,
157158
propertyBindings: true,
158159
flags: true,
159160
providerIndexes: true,

0 commit comments

Comments
 (0)