Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 1 addition & 6 deletions goldens/public-api/core/index.md
Original file line number Diff line number Diff line change
Expand Up @@ -418,11 +418,6 @@ export class DebugNode {
};
}

// @public
export type DeepReadonly<T> = T extends (infer R)[] ? ReadonlyArray<DeepReadonly<R>> : (T extends Function ? T : (T extends object ? {
readonly [P in keyof T]: DeepReadonly<T[P]>;
} : T));

// @public
export const DEFAULT_CURRENCY_CODE: InjectionToken<string>;

Expand Down Expand Up @@ -1366,7 +1361,7 @@ export interface SelfDecorator {
export function setTestabilityGetter(getter: GetTestability): void;

// @public
export type Signal<T> = (() => DeepReadonly<T>) & {
export type Signal<T> = (() => T) & {
[SIGNAL]: unknown;
};

Expand Down
1 change: 0 additions & 1 deletion packages/core/src/core_reactivity_export_internal.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,6 @@ export {
computed,
CreateComputedOptions,
CreateSignalOptions,
DeepReadonly,
isSignal,
Signal,
signal,
Expand Down
6 changes: 1 addition & 5 deletions packages/core/src/signals/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ This directory contains the code for Angular's reactive primitive, an implementa

## Conceptual surface

Angular Signals are zero-argument functions (`() => DeepReadonly<T>`). When executed, they return the current value of the signal. Executing signals does not trigger side effects, though it may lazily recompute intermediate values (lazy memoization).
Angular Signals are zero-argument functions (`() => T`). When executed, they return the current value of the signal. Executing signals does not trigger side effects, though it may lazily recompute intermediate values (lazy memoization).

Particular contexts (such as template expressions) can be _reactive_. In such contexts, executing a signal will return the value, but also register the signal as a dependency of the context in question. The context's owner will then be notified if any of its signal dependencies produces a new value (usually, this results in the re-execution of those expressions to consume the new values).

Expand Down Expand Up @@ -39,10 +39,6 @@ If the equality function determines that 2 values are equal it will:
* block update of signal’s value;
* skip change propagation.

#### `DeepReadonly`

Values read from signals are wrapped in a TypeScript type `DeepReadonly`, which recursively tags all properties and arrays in the inner type as `readonly`, preventing simple mutations. This acts as a safeguard against accidental mutation of signal values outside of the mutation API for `WritableSignal`s.

### Declarative derived values: `computed()`

`computed()` creates a memoizing signal, which calculates its value from the values of some number of input signals.
Expand Down
2 changes: 1 addition & 1 deletion packages/core/src/signals/index.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
*/

export {DeepReadonly, isSignal, Signal, ValueEqualityFn} from './src/api';
export {isSignal, Signal, ValueEqualityFn} from './src/api';
export {computed, CreateComputedOptions} from './src/computed';
export {setActiveConsumer} from './src/graph';
export {CreateSignalOptions, signal, WritableSignal} from './src/signal';
Expand Down
17 changes: 1 addition & 16 deletions packages/core/src/signals/src/api.ts
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@ const SIGNAL = Symbol('SIGNAL');
*
* @developerPreview
*/
export type Signal<T> = (() => DeepReadonly<T>)&{
export type Signal<T> = (() => T)&{
[SIGNAL]: unknown;
};

Expand Down Expand Up @@ -89,18 +89,3 @@ export function defaultEquals<T>(a: T, b: T) {
// as objects (`typeof null === 'object'`).
return (a === null || typeof a !== 'object') && Object.is(a, b);
}

// clang-format off
/**
* Makes `T` read-only at the property level.
*
* Objects have their properties mapped to `DeepReadonly` types and arrays are converted to
* `ReadonlyArray`s of `DeepReadonly` values.
*
* @developerPreview
*/
export type DeepReadonly<T> = T extends(infer R)[] ? ReadonlyArray<DeepReadonly<R>> :
(T extends Function ? T :
(T extends object ? {readonly[P in keyof T]: DeepReadonly<T[P]>} :
T));
// clang-format on
6 changes: 3 additions & 3 deletions packages/core/src/signals/src/signal.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 {createSignalFromFunction, DeepReadonly, defaultEquals, Signal, ValueEqualityFn} from './api';
import {createSignalFromFunction, defaultEquals, Signal, ValueEqualityFn} from './api';
import {ReactiveNode} from './graph';

/**
Expand Down Expand Up @@ -81,9 +81,9 @@ class WritableSignalImpl<T> extends ReactiveNode {
this.producerMayHaveChanged();
}

signal(): DeepReadonly<T> {
signal(): T {
this.producerAccessed();
return this.value as unknown as DeepReadonly<T>;
return this.value;
}
}

Expand Down
14 changes: 0 additions & 14 deletions packages/core/test/signals/signal_spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -97,18 +97,4 @@ describe('signals', () => {
state.set(stateValue);
expect(derived()).toEqual('object:5');
});

it('should prohibit mutable access to properties at a type level', () => {
const state = signal({name: 'John'});

// @ts-expect-error
state().name = 'Jacob';
});

it('should prohibit mutable access to arrays at a type level', () => {
const state = signal(['John']);

// @ts-expect-error
state().push('Jacob');
});
});