-
Notifications
You must be signed in to change notification settings - Fork 27.4k
feat(core): add support for two-way bindings on dynamically-created components #60342
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 our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -6,8 +6,10 @@ | |
| * found in the LICENSE file at https://angular.dev/license | ||
| */ | ||
|
|
||
| import {WritableSignal} from '../core_reactivity_export_internal'; | ||
| import {RuntimeError, RuntimeErrorCode} from '../errors'; | ||
| import {Type} from '../interface/type'; | ||
| import {Type, Writable} from '../interface/type'; | ||
| import {assertNotDefined} from '../util/assert'; | ||
| import {bindingUpdated} from './bindings'; | ||
| import {listenToDirectiveOutput, wrapListener} from './instructions/listener'; | ||
| import {setDirectiveInput, storePropertyBindingMetadata} from './instructions/shared'; | ||
|
|
@@ -174,3 +176,47 @@ export function outputBinding<T>(eventName: string, listener: (event: T) => unkn | |
|
|
||
| return binding; | ||
| } | ||
|
|
||
| /** | ||
| * Creates a two-way binding. | ||
| * @param eventName Public name of the two-way compatible input. | ||
| * @param value Writable signal from which to get the current value and to which to write new | ||
| * values. | ||
| * | ||
| * ### Usage example | ||
| * In this example we create an instance of the `MyCheckbox` component and bind to its `value` | ||
| * input using a two-way binding. | ||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Maybe not worth doing, but have we thought about some recommendations for type safety here? e.g. potentially users could do for public model fields twoWayBinding('bla', mySignal as TargetComponent['modelField']) |
||
| * | ||
| * ``` | ||
| * const checkboxValue = signal(''); | ||
| * | ||
| * createComponent(MyCheckbox, { | ||
| * bindings: [ | ||
| * twoWayBinding('value', checkboxValue), | ||
| * ], | ||
| * }); | ||
| * ``` | ||
| */ | ||
| export function twoWayBinding(publicName: string, value: WritableSignal<unknown>): Binding { | ||
| const input = inputBinding(publicName, value); | ||
| const output = outputBinding(publicName + 'Change', (eventValue) => value.set(eventValue)); | ||
|
|
||
| // We take advantage of inputs only having a `create` block and outputs only having an `update` | ||
| // block by passing them through directly instead of creating dedicated functions here. This | ||
| // assumption can break down if one of them starts targeting both blocks. These assertions | ||
| // are here to help us catch it if something changes in the future. | ||
| ngDevMode && assertNotDefined(input.create, 'Unexpected `create` callback in inputBinding'); | ||
| ngDevMode && assertNotDefined(output.update, 'Unexpected `update` callback in outputBinding'); | ||
|
|
||
| return { | ||
| [BINDING]: { | ||
| kind: 'twoWay', | ||
| requiredVars: input[BINDING].requiredVars + output[BINDING].requiredVars, | ||
| }, | ||
| set target(target: unknown) { | ||
|
Member
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I was debating whether to make this a setter or change |
||
| (input as Writable<Binding>).target = (output as Writable<Binding>).target = target; | ||
| }, | ||
| create: output.create, | ||
| update: input.update, | ||
| }; | ||
| } | ||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
nit: is it potentially worth to
{@link ...}to the API that this function is supposed to be used with?