|
6 | 6 | * found in the LICENSE file at https://angular.dev/license
|
7 | 7 | */
|
8 | 8 |
|
| 9 | +import {WritableSignal} from '../core_reactivity_export_internal'; |
9 | 10 | import {RuntimeError, RuntimeErrorCode} from '../errors';
|
10 |
| -import {Type} from '../interface/type'; |
| 11 | +import {Type, Writable} from '../interface/type'; |
| 12 | +import {assertNotDefined} from '../util/assert'; |
11 | 13 | import {bindingUpdated} from './bindings';
|
12 | 14 | import {listenToDirectiveOutput, wrapListener} from './instructions/listener';
|
13 | 15 | import {setDirectiveInput, storePropertyBindingMetadata} from './instructions/shared';
|
@@ -174,3 +176,47 @@ export function outputBinding<T>(eventName: string, listener: (event: T) => unkn
|
174 | 176 |
|
175 | 177 | return binding;
|
176 | 178 | }
|
| 179 | + |
| 180 | +/** |
| 181 | + * Creates a two-way binding. |
| 182 | + * @param eventName Public name of the two-way compatible input. |
| 183 | + * @param value Writable signal from which to get the current value and to which to write new |
| 184 | + * values. |
| 185 | + * |
| 186 | + * ### Usage example |
| 187 | + * In this example we create an instance of the `MyCheckbox` component and bind to its `value` |
| 188 | + * input using a two-way binding. |
| 189 | + * |
| 190 | + * ``` |
| 191 | + * const checkboxValue = signal(''); |
| 192 | + * |
| 193 | + * createComponent(MyCheckbox, { |
| 194 | + * bindings: [ |
| 195 | + * twoWayBinding('value', checkboxValue), |
| 196 | + * ], |
| 197 | + * }); |
| 198 | + * ``` |
| 199 | + */ |
| 200 | +export function twoWayBinding(publicName: string, value: WritableSignal<unknown>): Binding { |
| 201 | + const input = inputBinding(publicName, value); |
| 202 | + const output = outputBinding(publicName + 'Change', (eventValue) => value.set(eventValue)); |
| 203 | + |
| 204 | + // We take advantage of inputs only having a `create` block and outputs only having an `update` |
| 205 | + // block by passing them through directly instead of creating dedicated functions here. This |
| 206 | + // assumption can break down if one of them starts targeting both blocks. These assertions |
| 207 | + // are here to help us catch it if something changes in the future. |
| 208 | + ngDevMode && assertNotDefined(input.create, 'Unexpected `create` callback in inputBinding'); |
| 209 | + ngDevMode && assertNotDefined(output.update, 'Unexpected `update` callback in outputBinding'); |
| 210 | + |
| 211 | + return { |
| 212 | + [BINDING]: { |
| 213 | + kind: 'twoWay', |
| 214 | + requiredVars: input[BINDING].requiredVars + output[BINDING].requiredVars, |
| 215 | + }, |
| 216 | + set target(target: unknown) { |
| 217 | + (input as Writable<Binding>).target = (output as Writable<Binding>).target = target; |
| 218 | + }, |
| 219 | + create: output.create, |
| 220 | + update: input.update, |
| 221 | + }; |
| 222 | +} |
0 commit comments