Movatterモバイル変換


[0]ホーム

URL:


Skip to content

Navigation Menu

Sign in
Appearance settings

Search code, repositories, users, issues, pull requests...

Provide feedback

We read every piece of feedback, and take your input very seriously.

Saved searches

Use saved searches to filter your results more quickly

Sign up
Appearance settings

docs: add signal forms schema field configuration guide#66176

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to ourterms of service andprivacy statement. We’ll occasionally send you account related emails.

Already on GitHub?Sign in to your account

Open
bencodezen wants to merge1 commit intoangular:main
base:main
Choose a base branch
Loading
frombencodezen:docs/add-signal-forms-schema-config-guide

Conversation

@bencodezen
Copy link
Contributor

PR Checklist

Please check if your PR fulfills the following requirements:

PR Type

What kind of change does this PR introduce?

  • Bugfix
  • Feature
  • Code style update (formatting, local variables)
  • Refactoring (no functional changes, no api changes)
  • Build related changes
  • CI related changes
  • Documentation content changes
  • angular.dev application / infrastructure changes
  • Other... Please describe:

What is the current behavior?

Issue Number: N/A

What is the new behavior?

Does this PR introduce a breaking change?

  • Yes
  • No

Other information

michael-small reacted with heart emojimichael-small reacted with rocket emoji
@bencodezenbencodezen added action: reviewThe PR is still awaiting reviews from at least one requested reviewer target: patchThis PR is targeted for the next patch release area: docsRelated to the documentation labelsDec 18, 2025
@ngbotngbotbot added this to theBacklog milestoneDec 18, 2025
@github-actions
Copy link

Deployed adev-preview for720aa14 to:https://ng-dev-previews-fw--pr-angular-angular-66176-adev-prev-i5w2jvjx.web.app

Note: As new commits are pushed to this pull request, this link is updated after the preview is rebuilt.

@@ -0,0 +1,827 @@
#Schema field configuration
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others.Learn more.

Can we call this guide "Adding form logic" or something like that, and then say "signal forms allows you to add logic to your form using schemas" and just refer to it as "schemas" from the on instead of "schema field configuration"

msmallest reacted with thumbs up emojileonsenft reacted with heart emoji
@@ -0,0 +1,827 @@
#Schema field configuration

Signal Forms allow you to configure how fields behave beyond validation through the form schema. You can disable fields conditionally, hide them based on other values, make them readonly, debounce user input, and attach metadata for custom controls.
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others.Learn more.

This sounds like its saying that schemas are for logic other than validation, and you specify validation some other way. I think we could make it more clear by saying that validation logic is covered in [link] and this guide discusses other rules available in schemas

leonsenft reacted with thumbs up emoji

##How configuration functions work

Most configuration functions accept an optional callback function that makes the behavior reactive to form state. This means that the callback function runs automatically whenever the referenced field values change.
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others.Learn more.

Ah I see where the "configuration" stuff is coming from now. We call these rules, e.g. "thedisabled rule". I think a clearer way to say this is "Rules bind reactive logic to specific fields in your form. Most rules accept a reactive logic function as an optional argument. The reactive logic function automatically recomputes whenever the signals it references change, just like acomputed"

Maybe we could try to diagram out the anatomy or something:

disabled(schemaPath.couponCode, ({valueOf}) => valueOf(schemaPath.total) < 50);~~~~~~~~ ~~~~~~~~~~~~~~~~~~~~~  ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~rule     path                   reactive logic function

msmallest reacted with thumbs up emojileonsenft reacted with heart emoji
@bencodezenbencodezen added action: cleanupThe PR is in need of cleanup, either due to needing a rebase or in response to comments from reviews and removed action: reviewThe PR is still awaiting reviews from at least one requested reviewer labelsDec 18, 2025
@bencodezen
Copy link
ContributorAuthor

This is fantastic feedback@mmalerba! I played around with a few different terminology sets and wanted to see how this would land, so appreciate the candid thoughts. I'll make the changes accordingly before marking it ready for review again.

label:'Field state management',
path:'guide/forms/signals/field-state-management',
contentPath:'guide/forms/signals/field-state-management',
status:'new',
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others.Learn more.

Shouldn't this new'new' be on the'Schema field configuration' object?

Comment on lines +459 to +464
-`REQUIRED` - Whether the field is required (boolean)
-`MIN` - Minimum numeric value (number | undefined)
-`MAX` - Maximum numeric value (number | undefined)
-`MIN_LENGTH` - Minimum string/array length (number | undefined)
-`MAX_LENGTH` - Maximum string/array length (number | undefined)
-`PATTERN` - Regular expression pattern (RegExp[] - array to support multiple patterns)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others.Learn more.

nit:

Suggested change
-`REQUIRED` - Whether the field is required (boolean)
-`MIN` - Minimum numeric value (number | undefined)
-`MAX` - Maximum numeric value (number | undefined)
-`MIN_LENGTH` - Minimum string/array length (number | undefined)
-`MAX_LENGTH` - Maximum string/array length (number | undefined)
-`PATTERN` - Regular expression pattern (RegExp[] - array to support multiple patterns)
-`REQUIRED` - Whether the field is required (`boolean`)
-`MIN` - Minimum numeric value (`number | undefined`)
-`MAX` - Maximum numeric value (`number | undefined`)
-`MIN_LENGTH` - Minimum string/array length (`number | undefined`)
-`MAX_LENGTH` - Maximum string/array length (`number | undefined`)
-`PATTERN` - Regular expression pattern (`RegExp[]` - array to support multiple patterns)

Comment on lines +532 to +536
const score = value()
if (score < 0 || score > 100) {
return {kind: 'range', message: 'Score must be between 0 and 100'}
}
return null
Copy link
Member

@JeanMecheJeanMecheDec 18, 2025
edited
Loading

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others.Learn more.

Nit: I would expect the auto-formatting to add those.

Suggested change
const score = value()
if (score < 0 || score > 100) {
return {kind: 'range', message: 'Score must be between 0 and 100'}
}
return null
const score = value();
if (score < 0 || score > 100) {
return {kind: 'range', message: 'Score must be between 0 and 100'};
}
return null;

Edit: Oh okay, it doesn't because prettier don't understandangular-ts.
Anyway, it's just a nit.
It would be worth asking if prettier could support the angular syntax code block. I openedprettier/prettier#18500 for this.


This means users can type quickly, tab away, or submit the form without waiting for debounce delays to expire.

###Custom debounce logic
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others.Learn more.

This section is inaccurate. From thedurationOrDebouncer parameter documentation:

*@param durationOrDebouncer Either a debounce duration in milliseconds, or a custom
* {@link Debouncer} function.

TheDebouncer function isn't a reactive logic function. It's just a callback that is called every time the control value is updated. It's expected to return nothing (undefined) to immediately synchronize the update, or a promise that will prevent synchronization until it resolves.

For example, thedebounce() rule converts the duration to such a function:

constdebouncer=
typeofdurationOrDebouncer==='function'
?durationOrDebouncer
:durationOrDebouncer>0
?debounceForDuration(durationOrDebouncer)
:immediate;
pathNode.builder.addMetadataRule(DEBOUNCER,()=>debouncer);
}
functiondebounceForDuration(durationInMilliseconds:number):Debouncer<unknown>{
return(_context,abortSignal)=>{
returnnewPromise((resolve)=>{
consttimeoutId=setTimeout(resolve,durationInMilliseconds);
abortSignal.addEventListener('abort',()=>clearTimeout(timeoutId));
});
};
}
functionimmediate(){}

-`MAX_LENGTH` - Maximum string/array length (number | undefined)
-`PATTERN` - Regular expression pattern (RegExp[] - array to support multiple patterns)

When you use validation rules like`required()` or`min()`, they automatically set the corresponding metadata. Custom controls can read this metadata to configure HTML attributes.
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others.Learn more.

"configure HTML attributes" is overly specific and already done automatically for the built-in ones. I think it's sufficient to just saymetadata() provides a way to publish some additional data associated with a field.

IIRC@mmalerba had a good example for labels?

Comment on lines +485 to +487
[required]="isRequired()"
[min]="minValue()"
[max]="maxValue()"
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others.Learn more.

This won't compile. The type checker prevents the user from explicitly binding any of the built-in attributes when the[field] directive is present, because it'll automatically bind these for you.

})
// Manually set metadata for custom controls
metadata(schemaPath.score, MIN, () => 0)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others.Learn more.

I don't think we want to encourage setting built-in metadata this way.min(schemaPath.score, 0) is preferable in this case.


###Managed metadata with reducers

For more complex metadata that needs to accumulate values, use`createManagedMetadataKey()` with a reducer function:
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others.Learn more.

@mmalerba correct me if I'm wrong, but this isn't the purpose of the "managed" key. All metadata keys support reducers for accumulating values if desired. The "managed" variant is for data that wants to compute a new valuefrom the accumulated/reduced value.

</label>
<label>
Quantity (max: {{ maxQuantity() }})
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others.Learn more.

Great example for a custom metadata key, but sinceMAX is built-in, there's already a signal that exposes it directly:

Suggested change
Quantity (max: {{maxQuantity() }})
Quantity (max: {{inventoryForm.quantity().max() }})

<input
type="number"
[value]="value()"
(input)="value.set(($event.target as HTMLInputElement).valueAsNumber)"
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others.Learn more.

value.set won't work here sincevalue is a computed which are readonly.

I'd just go through the field here:

[value]="field().value()"(input)="field().value.set(...)"

Comment on lines +668 to +670
[min]="minValue()"
[max]="maxValue()"
[required]="isRequired()"
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others.Learn more.

Again, since these three metadata are built-in, you can just use the built-in properties to access them:

Suggested change
[min]="minValue()"
[max]="maxValue()"
[required]="isRequired()"
[min]="field().min()"
[max]="field().max()"
[required]="field().required()"

(schemaPath) => {
// Only applied when country is US
required(schemaPath.zipCode)
metadata(schemaPath.zipCode, PATTERN, () => /^\d{5}(-\d{4})?$/)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others.Learn more.

Suggested change
metadata(schemaPath.zipCode, PATTERN, () => /^\d{5}(-\d{4})?$/)
pattern(schemaPath.zipCode, /^\d{5}(-\d{4})?$/)

function emailFieldConfig(path: SchemaPath<string>) {
debounce(path, 300)
metadata(path, PLACEHOLDER, () => 'user@example.com')
metadata(path, MAX_LENGTH, () => 255)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others.Learn more.

Suggested change
metadata(path, MAX_LENGTH, () => 255)
maxLength(path, 255)

Sign up for freeto join this conversation on GitHub. Already have an account?Sign in to comment

Reviewers

@JeanMecheJeanMecheJeanMeche left review comments

@mmalerbammalerbammalerba left review comments

@leonsenftleonsenftleonsenft requested changes

@kirjskirjsAwaiting requested review from kirjs

+1 more reviewer

@msmallestmsmallestmsmallest left review comments

Reviewers whose approvals may not affect merge requirements

Assignees

No one assigned

Labels

action: cleanupThe PR is in need of cleanup, either due to needing a rebase or in response to comments from reviewsadev: previewarea: docsRelated to the documentationtarget: patchThis PR is targeted for the next patch release

Projects

None yet

Milestone

Backlog

Development

Successfully merging this pull request may close these issues.

5 participants

@bencodezen@JeanMeche@leonsenft@mmalerba@msmallest

[8]ページ先頭

©2009-2025 Movatter.jp