Skip to content

Commit

Permalink
fix(core): return a readonly signal on asReadonly. (#54706)
Browse files Browse the repository at this point in the history
Previous `asReadonly()` returned the signal value and not the signal itself.

Fixes #54704

PR Close #54706
  • Loading branch information
JeanMeche authored and pkozlowski-opensource committed Mar 6, 2024
1 parent e5885fa commit 7243c70
Show file tree
Hide file tree
Showing 3 changed files with 14 additions and 4 deletions.
4 changes: 2 additions & 2 deletions packages/core/src/authoring/model/model_signal.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ import {producerAccessed, SIGNAL, signalSetFn} from '@angular/core/primitives/si

import {RuntimeError, RuntimeErrorCode} from '../../errors';
import {Signal} from '../../render3/reactivity/api';
import {WritableSignal, ɵWRITABLE_SIGNAL} from '../../render3/reactivity/signal';
import {signalAsReadonlyFn, WritableSignal, ɵWRITABLE_SIGNAL} from '../../render3/reactivity/signal';
import {ɵINPUT_SIGNAL_BRAND_READ_TYPE, ɵINPUT_SIGNAL_BRAND_WRITE_TYPE} from '../input/input_signal';
import {INPUT_SIGNAL_NODE, InputSignalNode, REQUIRED_UNSET_VALUE} from '../input/input_signal_node';
import {OutputEmitterRef} from '../output/output_emitter_ref';
Expand Down Expand Up @@ -63,7 +63,7 @@ export function createModelSignal<T>(initialValue: T): ModelSignal<T> {
}

getter[SIGNAL] = node;
getter.asReadonly = (() => getter()) as () => Signal<T>;
getter.asReadonly = signalAsReadonlyFn.bind(getter as any) as () => Signal<T>;

// TODO: Should we throw an error when updating a destroyed model?
getter.set = (newValue: T) => {
Expand Down
2 changes: 1 addition & 1 deletion packages/core/src/render3/reactivity/signal.ts
Original file line number Diff line number Diff line change
Expand Up @@ -80,7 +80,7 @@ export function signal<T>(initialValue: T, options?: CreateSignalOptions<T>): Wr
return signalFn as WritableSignal<T>;
}

function signalAsReadonlyFn<T>(this: SignalGetter<T>): Signal<T> {
export function signalAsReadonlyFn<T>(this: SignalGetter<T>): Signal<T> {
const node = this[SIGNAL] as SignalNode<T>& {readonlyFn?: Signal<T>};
if (node.readonlyFn === undefined) {
const readonlyFn = () => this();
Expand Down
12 changes: 11 additions & 1 deletion packages/core/test/authoring/model_input_spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@
* found in the LICENSE file at https://angular.io/license
*/

import {computed, model} from '@angular/core';
import {computed, isSignal, model, WritableSignal} from '@angular/core';
import {TestBed} from '@angular/core/testing';

describe('model signal', () => {
Expand Down Expand Up @@ -35,6 +35,16 @@ describe('model signal', () => {
expect(signal()).toBe(18);
});

it('should return readonly signal', () => {
const signal = TestBed.runInInjectionContext(() => model(2));
const readOnly = signal.asReadonly();

expect(isSignal(readOnly)).toBeTrue();
expect(readOnly()).toBe(2);
expect((readOnly as WritableSignal<unknown>).set).toBeUndefined();
expect((readOnly as WritableSignal<unknown>).update).toBeUndefined();
});

it('should emit when the value changes', () => {
const signal = TestBed.runInInjectionContext(() => model(1));
const values: number[] = [];
Expand Down

0 comments on commit 7243c70

Please sign in to comment.