diff --git a/packages/core/src/render3/instructions/control.ts b/packages/core/src/render3/instructions/control.ts index cb281cdde0a..5290452cde9 100644 --- a/packages/core/src/render3/instructions/control.ts +++ b/packages/core/src/render3/instructions/control.ts @@ -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(value: T, sanitizer?: SanitizerFn | null): void { const HAS_CONTROL_MASK = /* @__PURE__ */ (() => TNodeFlags.isNativeControl | TNodeFlags.isFormValueControl | TNodeFlags.isFormCheckboxControl)(); -function getControlDirectiveFirstCreatePass( - tView: TView, - tNode: TNode, - lView: LView, -): ɵControl | undefined { +function initializeControlFirstCreatePass(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( } // Search for the `ɵControl` directive. - const control = findControlDirective(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; + tNode.fieldIndex = controlIndex; if (isComponentHost(tNode)) { const componentDef = tView.data[componentIndex] as ComponentDef; @@ -158,7 +169,7 @@ function getControlDirectiveFirstCreatePass( // 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( } if (tNode.flags & HAS_CONTROL_MASK) { - return control; + return; } const tagName = tNode.value; @@ -193,25 +204,9 @@ function getControlDirectiveFirstCreatePass( * @param tNode The `TNode` of the element to check. * @param lView The `LView` that contains the element. */ -function getControlDirective(tNode: TNode, lView: LView): ɵControl | undefined { - return tNode.flags & TNodeFlags.isFormControl - ? findControlDirective(lView, tNode.inputs!['field']) - : undefined; -} - -function findControlDirective( - lView: LView, - directiveIndices: number[], -): ɵControl | 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(tNode: TNode, lView: LView): ɵControl | null { + const index = tNode.fieldIndex; + return index === -1 ? null : lView[index]; } /** Returns whether the specified `componentDef` has a model input named `name`. */ diff --git a/packages/core/src/render3/interfaces/node.ts b/packages/core/src/render3/interfaces/node.ts index 669bc36a7aa..6a7204275df 100644 --- a/packages/core/src/render3/interfaces/node.ts +++ b/packages/core/src/render3/interfaces/node.ts @@ -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. * diff --git a/packages/core/src/render3/tnode_manipulation.ts b/packages/core/src/render3/tnode_manipulation.ts index cebc3f4295b..5509e6a3332 100644 --- a/packages/core/src/render3/tnode_manipulation.ts +++ b/packages/core/src/render3/tnode_manipulation.ts @@ -298,6 +298,7 @@ export function createTNode( directiveEnd: -1, directiveStylingLast: -1, componentOffset: -1, + fieldIndex: -1, propertyBindings: null, flags, providerIndexes: 0, diff --git a/packages/core/test/render3/is_shape_of.ts b/packages/core/test/render3/is_shape_of.ts index f0207782f67..6b993fe2cc9 100644 --- a/packages/core/test/render3/is_shape_of.ts +++ b/packages/core/test/render3/is_shape_of.ts @@ -154,6 +154,7 @@ const ShapeOfTNode: ShapeOf = { directiveEnd: true, directiveStylingLast: true, componentOffset: true, + fieldIndex: true, propertyBindings: true, flags: true, providerIndexes: true,