Skip to content

Commit 3faf3e2

Browse files
crisbetothePunderWoman
authored andcommitted
refactor(core): implement two-way instructions (angular#54252)
Adds the implementations of the `twoWayProperty` and `twoWayListener` instructions. PR Close angular#54252
1 parent 06fa029 commit 3faf3e2

3 files changed

Lines changed: 38 additions & 9 deletions

File tree

packages/core/src/render3/instructions/listener.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -113,7 +113,7 @@ function findExistingListener(
113113
return null;
114114
}
115115

116-
function listenerInternal(
116+
export function listenerInternal(
117117
tView: TView, lView: LView<{}|null>, renderer: Renderer, tNode: TNode, eventName: string,
118118
listenerFn: (e?: any) => any, eventTargetResolver?: GlobalTargetResolver): void {
119119
const isTNodeDirectiveHost = isDirectiveHost(tNode);

packages/core/src/render3/instructions/two_way.ts

Lines changed: 29 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -6,10 +6,15 @@
66
* found in the LICENSE file at https://angular.io/license
77
*/
88

9+
import {bindingUpdated} from '../bindings';
910
import {SanitizerFn} from '../interfaces/sanitization';
11+
import {RENDERER} from '../interfaces/view';
12+
import {isSignal} from '../reactivity/api';
13+
import {isWritableSignal} from '../reactivity/signal';
14+
import {getCurrentTNode, getLView, getSelectedTNode, getTView, nextBindingIndex} from '../state';
1015

11-
import {ɵɵlistener} from './listener';
12-
import {ɵɵproperty} from './property';
16+
import {listenerInternal} from './listener';
17+
import {elementPropertyInternal, storePropertyBindingMetadata} from './shared';
1318

1419

1520
/**
@@ -27,8 +32,21 @@ import {ɵɵproperty} from './property';
2732
*/
2833
export function ɵɵtwoWayProperty<T>(
2934
propName: string, value: T, sanitizer?: SanitizerFn|null): typeof ɵɵtwoWayProperty {
30-
// TODO(crisbeto): implement two-way specific logic.
31-
ɵɵproperty(propName, value, sanitizer);
35+
// TODO(crisbeto): perf impact of re-evaluating this on each change detection?
36+
if (isSignal(value)) {
37+
value = value() as T;
38+
}
39+
40+
const lView = getLView();
41+
const bindingIndex = nextBindingIndex();
42+
if (bindingUpdated(lView, bindingIndex, value)) {
43+
const tView = getTView();
44+
const tNode = getSelectedTNode();
45+
elementPropertyInternal(
46+
tView, tNode, lView, propName, value, lView[RENDERER], sanitizer, false);
47+
ngDevMode && storePropertyBindingMetadata(tView.data, tNode, propName, bindingIndex);
48+
}
49+
3250
return ɵɵtwoWayProperty;
3351
}
3452

@@ -41,8 +59,9 @@ export function ɵɵtwoWayProperty<T>(
4159
* @codeGenApi
4260
*/
4361
export function ɵɵtwoWayBindingSet<T>(target: unknown, value: T): boolean {
44-
// TODO(crisbeto): implement this fully.
45-
return false;
62+
const canWrite = isWritableSignal(target);
63+
canWrite && target.set(value);
64+
return canWrite;
4665
}
4766

4867
/**
@@ -55,6 +74,9 @@ export function ɵɵtwoWayBindingSet<T>(target: unknown, value: T): boolean {
5574
*/
5675
export function ɵɵtwoWayListener(
5776
eventName: string, listenerFn: (e?: any) => any): typeof ɵɵtwoWayListener {
58-
ɵɵlistener(eventName, listenerFn);
77+
const lView = getLView<{}|null>();
78+
const tView = getTView();
79+
const tNode = getCurrentTNode()!;
80+
listenerInternal(tView, lView, lView[RENDERER], tNode, eventName, listenerFn);
5981
return ɵɵtwoWayListener;
6082
}

packages/core/src/render3/reactivity/signal.ts

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@
88

99
import {createSignal, SIGNAL, SignalGetter, SignalNode, signalSetFn, signalUpdateFn} from '@angular/core/primitives/signals';
1010

11-
import {Signal, ValueEqualityFn} from './api';
11+
import {isSignal, Signal, ValueEqualityFn} from './api';
1212

1313
/**
1414
* A `Signal` with a value that can be mutated via a setter interface.
@@ -71,3 +71,10 @@ function signalAsReadonlyFn<T>(this: SignalGetter<T>): Signal<T> {
7171
}
7272
return node.readonlyFn;
7373
}
74+
75+
/**
76+
* Checks if the given `value` is a writeable signal.
77+
*/
78+
export function isWritableSignal(value: unknown): value is WritableSignal<unknown> {
79+
return isSignal(value) && typeof (value as any).set === 'function';
80+
}

0 commit comments

Comments
 (0)