Thanks to visit codestin.com
Credit goes to github.com

Skip to content

Commit e682f00

Browse files
leonsenftthePunderWoman
authored andcommitted
refactor(forms): reduce boilerplate needed to define custom controls
An early piece of feedback received regarding custom controls hosted on native inputs was that they required a lot of boilerplate to bind `FieldState` properties. Each property required an input to accept the property, and a host binding to forward it to the native control. (cherry picked from commit c727df5)
1 parent 780e372 commit e682f00

File tree

3 files changed

+252
-22
lines changed

3 files changed

+252
-22
lines changed

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

Lines changed: 19 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -110,6 +110,10 @@ export function ɵɵcontrol<T>(value: T, sanitizer?: SanitizerFn | null): void {
110110
nextBindingIndex();
111111
}
112112

113+
/** A bitmask used to check if a TNode represents a native or custom form control. */
114+
const HAS_CONTROL_MASK = /* @__PURE__ */ (() =>
115+
TNodeFlags.isNativeControl | TNodeFlags.isFormValueControl | TNodeFlags.isFormCheckboxControl)();
116+
113117
function getControlDirectiveFirstCreatePass<T>(
114118
tView: TView,
115119
tNode: TNode,
@@ -143,28 +147,31 @@ function getControlDirectiveFirstCreatePass<T>(
143147

144148
if (isComponentHost(tNode)) {
145149
const componentDef = tView.data[componentIndex] as ComponentDef<unknown>;
146-
// TODO: should we check that any additional field state inputs are signal based?
147150
if (hasModelInput(componentDef, 'value')) {
148151
tNode.flags |= TNodeFlags.isFormValueControl;
149-
return control;
150152
} else if (hasModelInput(componentDef, 'checked')) {
151153
tNode.flags |= TNodeFlags.isFormCheckboxControl;
152-
return control;
153154
}
155+
// Continue on to check if the host element is also a native control.
154156
}
155157

156-
if (control.ɵinteropControl) {
158+
// Only check for an interop control if we haven't already found a custom one.
159+
if (!(tNode.flags & HAS_CONTROL_MASK) && control.ɵinteropControl) {
157160
tNode.flags |= TNodeFlags.isInteropControl;
158161
return control;
159162
}
160163

161164
if (isNativeControl(tNode)) {
165+
tNode.flags |= TNodeFlags.isNativeControl;
162166
if (isNumericInput(tNode)) {
163167
tNode.flags |= TNodeFlags.isNativeNumericControl;
164168
}
165169
if (isTextControl(tNode)) {
166170
tNode.flags |= TNodeFlags.isNativeTextControl;
167171
}
172+
}
173+
174+
if (tNode.flags & HAS_CONTROL_MASK) {
168175
return control;
169176
}
170177

@@ -454,6 +461,14 @@ function updateCustomControl(
454461
const inputName = CONTROL_BINDING_NAMES[key];
455462
maybeUpdateInput(componentDef, component, bindings, state, key, inputName);
456463
}
464+
465+
// If the host node is a native control, we can bind field state properties to attributes for any
466+
// that weren't defined as inputs on the custom control. We can reuse the update path for native
467+
// controls since any properties with a corresponding input would have just been checked above,
468+
// and thus will appear unchanged.
469+
if (tNode.flags & TNodeFlags.isNativeControl) {
470+
updateNativeControl(tNode, lView, control);
471+
}
457472
}
458473

459474
/**

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

Lines changed: 26 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -131,88 +131,96 @@ export function isLetDeclaration(tNode: TNode): boolean {
131131
*/
132132
export const enum TNodeFlags {
133133
/** Bit #1 - This bit is set if the node is a host for any directive (including a component) */
134-
isDirectiveHost = 0x1,
134+
isDirectiveHost = 1 << 0,
135135

136136
/** Bit #2 - This bit is set if the node has been projected */
137-
isProjected = 0x2,
137+
isProjected = 1 << 1,
138138

139139
/** Bit #3 - This bit is set if any directive on this node has content queries */
140-
hasContentQuery = 0x4,
140+
hasContentQuery = 1 << 2,
141141

142142
/** Bit #4 - This bit is set if the node has any "class" inputs */
143-
hasClassInput = 0x8,
143+
hasClassInput = 1 << 3,
144144

145145
/** Bit #5 - This bit is set if the node has any "style" inputs */
146-
hasStyleInput = 0x10,
146+
hasStyleInput = 1 << 4,
147147

148148
/** Bit #6 - This bit is set if the node has been detached by i18n */
149-
isDetached = 0x20,
149+
isDetached = 1 << 5,
150150

151151
/**
152152
* Bit #7 - This bit is set if the node has directives with host bindings.
153153
*
154154
* This flags allows us to guard host-binding logic and invoke it only on nodes
155155
* that actually have directives with host bindings.
156156
*/
157-
hasHostBindings = 0x40,
157+
hasHostBindings = 1 << 6,
158158

159159
/**
160160
* Bit #8 - This bit is set if the node is a located inside skip hydration block.
161161
*/
162-
inSkipHydrationBlock = 0x80,
162+
inSkipHydrationBlock = 1 << 7,
163163

164164
/**
165165
* Bit #9 - This bit is set if the node is a start of a set of control flow blocks.
166166
*/
167-
isControlFlowStart = 0x100,
167+
isControlFlowStart = 1 << 8,
168168

169169
/**
170170
* Bit #10 - This bit is set if the node is within a set of control flow blocks.
171171
*/
172-
isInControlFlow = 0x200,
172+
isInControlFlow = 1 << 9,
173173

174174
/**
175175
* Bit #11 - This bit is set if the node represents a form control.
176176
*
177177
* True when the node has an input binding to a `ɵControl` directive (but not also to a custom
178178
* component).
179179
*/
180-
isFormControl = 0x400,
180+
isFormControl = 1 << 10,
181181

182182
/**
183183
* Bit #12 - This bit is set if the node hosts a custom control component.
184184
*
185185
* A custom control component's model property is named `value`.
186186
*/
187-
isFormValueControl = 0x800,
187+
isFormValueControl = 1 << 11,
188188

189189
/**
190190
* Bit #13 - This bit is set if the node hosts a custom checkbox component.
191191
*
192192
* A custom checkbox component's model property is named `checked`.
193193
*/
194-
isFormCheckboxControl = 0x1000,
194+
isFormCheckboxControl = 1 << 12,
195195

196196
/**
197197
* Bit #14 - This bit is set if the node hosts an interoperable control implementation.
198198
*
199199
* This is used to bind to a `ControlValueAccessor` from `@angular/forms`.
200200
*/
201-
isInteropControl = 0x2000,
201+
isInteropControl = 1 << 13,
202202

203203
/**
204-
* Bit #15 - 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.
205+
*
206+
* This is used to determine whether we can bind common control properties to the host element of
207+
* a custom control when it doesn't define a corresponding input.
208+
*/
209+
isNativeControl = 1 << 14,
210+
211+
/**
212+
* Bit #16 - This bit is set if the node is a native control with a numeric type.
205213
*
206214
* This is used to determine whether the control supports the `min` and `max` properties.
207215
*/
208-
isNativeNumericControl = 0x4000,
216+
isNativeNumericControl = 1 << 15,
209217

210218
/**
211-
* Bit #16 - This bit is set if the node is a native text control.
219+
* Bit #17 - This bit is set if the node is a native text control.
212220
*
213221
* This is used to determine whether control supports the `minLength` and `maxLength` properties.
214222
*/
215-
isNativeTextControl = 0x8000,
223+
isNativeTextControl = 1 << 16,
216224
}
217225

218226
/**

0 commit comments

Comments
 (0)