mirror of
https://github.com/angular/angular
synced 2026-05-24 09:28:37 +00:00
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.
This commit is contained in:
parent
81ce1ba1d9
commit
5e6d8573f4
4 changed files with 47 additions and 52 deletions
|
|
@ -7,6 +7,7 @@
|
|||
*/
|
||||
import {RuntimeError, RuntimeErrorCode} from '../../errors';
|
||||
import {getClosureSafeProperty} from '../../util/property';
|
||||
import {assertFirstCreatePass} from '../assert';
|
||||
import {bindingUpdated} from '../bindings';
|
||||
import {ɵCONTROL, ɵControl, ɵFieldState} from '../interfaces/control';
|
||||
import {ComponentDef} from '../interfaces/definition';
|
||||
|
|
@ -47,9 +48,12 @@ export function ɵɵcontrolCreate(): void {
|
|||
const lView = getLView<{} | null>();
|
||||
const tView = getTView();
|
||||
const tNode = getCurrentTNode()!;
|
||||
const control = tView.firstCreatePass
|
||||
? getControlDirectiveFirstCreatePass(tView, tNode, lView)
|
||||
: getControlDirective(tNode, lView);
|
||||
|
||||
if (tView.firstCreatePass) {
|
||||
initializeControlFirstCreatePass(tView, tNode, lView);
|
||||
}
|
||||
|
||||
const control = getControlDirective(tNode, lView);
|
||||
|
||||
if (!control) {
|
||||
return;
|
||||
|
|
@ -114,11 +118,9 @@ export function ɵɵcontrol<T>(value: T, sanitizer?: SanitizerFn | null): void {
|
|||
const HAS_CONTROL_MASK = /* @__PURE__ */ (() =>
|
||||
TNodeFlags.isNativeControl | TNodeFlags.isFormValueControl | TNodeFlags.isFormCheckboxControl)();
|
||||
|
||||
function getControlDirectiveFirstCreatePass<T>(
|
||||
tView: TView,
|
||||
tNode: TNode,
|
||||
lView: LView,
|
||||
): ɵControl<T> | undefined {
|
||||
function initializeControlFirstCreatePass<T>(tView: TView, tNode: TNode, lView: LView): void {
|
||||
ngDevMode && assertFirstCreatePass(tView);
|
||||
|
||||
const directiveIndices = tNode.inputs?.['field'];
|
||||
if (!directiveIndices) {
|
||||
// There are no matching inputs for the `[field]` property binding.
|
||||
|
|
@ -137,13 +139,22 @@ function getControlDirectiveFirstCreatePass<T>(
|
|||
}
|
||||
|
||||
// Search for the `ɵControl` directive.
|
||||
const control = findControlDirective<T>(lView, directiveIndices);
|
||||
if (!control) {
|
||||
let controlIndex = -1;
|
||||
|
||||
for (let index of directiveIndices) {
|
||||
if (ɵCONTROL in lView[index]) {
|
||||
controlIndex = index;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (controlIndex === -1) {
|
||||
// The `ɵControl` directive was not imported by this component.
|
||||
return;
|
||||
}
|
||||
|
||||
tNode.flags |= TNodeFlags.isFormControl;
|
||||
const control = lView[controlIndex] as ɵControl<T>;
|
||||
tNode.fieldIndex = controlIndex;
|
||||
|
||||
if (isComponentHost(tNode)) {
|
||||
const componentDef = tView.data[componentIndex] as ComponentDef<unknown>;
|
||||
|
|
@ -158,7 +169,7 @@ function getControlDirectiveFirstCreatePass<T>(
|
|||
// Only check for an interop control if we haven't already found a custom one.
|
||||
if (!(tNode.flags & HAS_CONTROL_MASK) && control.ɵinteropControl) {
|
||||
tNode.flags |= TNodeFlags.isInteropControl;
|
||||
return control;
|
||||
return;
|
||||
}
|
||||
|
||||
if (isNativeControl(tNode)) {
|
||||
|
|
@ -172,7 +183,7 @@ function getControlDirectiveFirstCreatePass<T>(
|
|||
}
|
||||
|
||||
if (tNode.flags & HAS_CONTROL_MASK) {
|
||||
return control;
|
||||
return;
|
||||
}
|
||||
|
||||
const tagName = tNode.value;
|
||||
|
|
@ -193,25 +204,9 @@ function getControlDirectiveFirstCreatePass<T>(
|
|||
* @param tNode The `TNode` of the element to check.
|
||||
* @param lView The `LView` that contains the element.
|
||||
*/
|
||||
function getControlDirective<T>(tNode: TNode, lView: LView): ɵControl<T> | undefined {
|
||||
return tNode.flags & TNodeFlags.isFormControl
|
||||
? findControlDirective(lView, tNode.inputs!['field'])
|
||||
: undefined;
|
||||
}
|
||||
|
||||
function findControlDirective<T>(
|
||||
lView: LView,
|
||||
directiveIndices: number[],
|
||||
): ɵControl<T> | undefined {
|
||||
for (let index of directiveIndices) {
|
||||
const directive = lView[index];
|
||||
if (ɵCONTROL in directive) {
|
||||
return directive;
|
||||
}
|
||||
}
|
||||
|
||||
// The `Field` directive was not imported by this component.
|
||||
return;
|
||||
function getControlDirective<T>(tNode: TNode, lView: LView): ɵControl<T> | null {
|
||||
const index = tNode.fieldIndex;
|
||||
return index === -1 ? null : lView[index];
|
||||
}
|
||||
|
||||
/** Returns whether the specified `componentDef` has a model input named `name`. */
|
||||
|
|
|
|||
|
|
@ -172,55 +172,47 @@ export const enum TNodeFlags {
|
|||
isInControlFlow = 1 << 9,
|
||||
|
||||
/**
|
||||
* Bit #11 - This bit is set if the node represents a form control.
|
||||
*
|
||||
* True when the node has an input binding to a `ɵControl` directive (but not also to a custom
|
||||
* component).
|
||||
*/
|
||||
isFormControl = 1 << 10,
|
||||
|
||||
/**
|
||||
* Bit #12 - This bit is set if the node hosts a custom control component.
|
||||
* Bit #11 - This bit is set if the node hosts a custom control component.
|
||||
*
|
||||
* A custom control component's model property is named `value`.
|
||||
*/
|
||||
isFormValueControl = 1 << 11,
|
||||
isFormValueControl = 1 << 10,
|
||||
|
||||
/**
|
||||
* Bit #13 - This bit is set if the node hosts a custom checkbox component.
|
||||
* Bit #12 - This bit is set if the node hosts a custom checkbox component.
|
||||
*
|
||||
* A custom checkbox component's model property is named `checked`.
|
||||
*/
|
||||
isFormCheckboxControl = 1 << 12,
|
||||
isFormCheckboxControl = 1 << 11,
|
||||
|
||||
/**
|
||||
* Bit #14 - This bit is set if the node hosts an interoperable control implementation.
|
||||
* Bit #13 - This bit is set if the node hosts an interoperable control implementation.
|
||||
*
|
||||
* This is used to bind to a `ControlValueAccessor` from `@angular/forms`.
|
||||
*/
|
||||
isInteropControl = 1 << 13,
|
||||
isInteropControl = 1 << 12,
|
||||
|
||||
/**
|
||||
* Bit #15 - This bit is set if the node is a native control.
|
||||
* Bit #14 - This bit is set if the node is a native control.
|
||||
*
|
||||
* This is used to determine whether we can bind common control properties to the host element of
|
||||
* a custom control when it doesn't define a corresponding input.
|
||||
*/
|
||||
isNativeControl = 1 << 14,
|
||||
isNativeControl = 1 << 13,
|
||||
|
||||
/**
|
||||
* Bit #16 - This bit is set if the node is a native control with a numeric type.
|
||||
* Bit #15 - This bit is set if the node is a native control with a numeric type.
|
||||
*
|
||||
* This is used to determine whether the control supports the `min` and `max` properties.
|
||||
*/
|
||||
isNativeNumericControl = 1 << 15,
|
||||
isNativeNumericControl = 1 << 14,
|
||||
|
||||
/**
|
||||
* Bit #17 - This bit is set if the node is a native text control.
|
||||
* Bit #16 - This bit is set if the node is a native text control.
|
||||
*
|
||||
* This is used to determine whether control supports the `minLength` and `maxLength` properties.
|
||||
*/
|
||||
isNativeTextControl = 1 << 16,
|
||||
isNativeTextControl = 1 << 15,
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
@ -381,6 +373,12 @@ export interface TNode {
|
|||
*/
|
||||
componentOffset: number;
|
||||
|
||||
/**
|
||||
* Index at which the signal forms field directive is stored.
|
||||
* Value is set to -1 if there are no field directives.
|
||||
*/
|
||||
fieldIndex: number;
|
||||
|
||||
/**
|
||||
* Stores the last directive which had a styling instruction.
|
||||
*
|
||||
|
|
|
|||
|
|
@ -298,6 +298,7 @@ export function createTNode(
|
|||
directiveEnd: -1,
|
||||
directiveStylingLast: -1,
|
||||
componentOffset: -1,
|
||||
fieldIndex: -1,
|
||||
propertyBindings: null,
|
||||
flags,
|
||||
providerIndexes: 0,
|
||||
|
|
|
|||
|
|
@ -154,6 +154,7 @@ const ShapeOfTNode: ShapeOf<TNode> = {
|
|||
directiveEnd: true,
|
||||
directiveStylingLast: true,
|
||||
componentOffset: true,
|
||||
fieldIndex: true,
|
||||
propertyBindings: true,
|
||||
flags: true,
|
||||
providerIndexes: true,
|
||||
|
|
|
|||
Loading…
Reference in a new issue