Skip to content
Open
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
20 changes: 14 additions & 6 deletions packages/core/src/render3/reactivity/signal.ts
Original file line number Diff line number Diff line change
Expand Up @@ -69,23 +69,31 @@ export interface CreateSignalOptions<T> {
* Create a `Signal` that can be set or updated directly.
* @see [Angular Signals](guide/signals)
*/
export function signal<T>(initialValue: T, options?: CreateSignalOptions<T>): WritableSignal<T> {
const [get, set, update] = createSignal(initialValue, options?.equal);

const signalFn = get as SignalGetter<T> & WritableSignal<T>;
export function signal<T = undefined>(): WritableSignal<T | undefined>;
export function signal<T>(initialValue: T, options?: CreateSignalOptions<T>): WritableSignal<T>;
export function signal<T>(
initialValue?: T,
options?: CreateSignalOptions<T>,
): WritableSignal<T | undefined> {
const [get, set, update] = createSignal<T | undefined>(
initialValue,
options?.equal as ValueEqualityFn<T | undefined>,
);

const signalFn = get as SignalGetter<T | undefined> & WritableSignal<T | undefined>;
const node = signalFn[SIGNAL];

signalFn.set = set;
signalFn.update = update;
signalFn.asReadonly = signalAsReadonlyFn.bind(signalFn as any) as () => Signal<T>;
signalFn.asReadonly = signalAsReadonlyFn.bind(signalFn as any) as () => Signal<T | undefined>;

if (typeof ngDevMode !== 'undefined' && ngDevMode) {
const debugName = options?.debugName;
node.debugName = debugName;
signalFn.toString = () => `[Signal${debugName ? ' (' + debugName + ')' : ''}: ${signalFn()}]`;
}

return signalFn as WritableSignal<T>;
return signalFn as WritableSignal<T | undefined>;
}

export function signalAsReadonlyFn<T>(this: SignalGetter<T>): Signal<T> {
Expand Down
1 change: 1 addition & 0 deletions packages/core/test/authoring/BUILD.bazel
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ ts_project(
"signal_input_signature_test.ts",
"signal_model_signature_test.ts",
"signal_queries_signature_test.ts",
"signal_signature_test.ts",
"simple_changes_signature_test.ts",
"unwrap_writable_signal_signature_test.ts",
],
Expand Down
47 changes: 47 additions & 0 deletions packages/core/test/authoring/signal_signature_test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
/**
* @license
* Copyright Google LLC All Rights Reserved.
*
* Use of this source code is governed by an MIT-style license that can be
* found in the LICENSE file at https://angular.dev/license
*/

/**
* @fileoverview
* This file contains various `signal()` patterns and ensures
* the resulting types match our expectations (via comments asserting the `.d.ts`).
*/

import {signal} from '@angular/core';
// import preserved to simplify `.d.ts` emit and simplify the `type_tester` logic.
// tslint:disable-next-line no-duplicate-imports
import {WritableSignal} from '@angular/core';

export class SignalSignatureTest {
/** boolean */
inferredBoolean = signal(false);

/** string */
inferredString = signal('hello');

/** number */
inferredNumber = signal(0);

/** undefined */
explicitUndefinedValue = signal(undefined);

/** undefined */
noArgNoType = signal();

/** string | undefined */
noArgExplicitType = signal<string>();

/** string | undefined */
noArgExplicitUnionType = signal<string | undefined>();

/** string */
explicitTypeWithValue = signal<string>('hello');

/** string */
withEqualOption = signal('hello', {equal: (a, b) => a === b});
}
7 changes: 7 additions & 0 deletions packages/core/test/signals/signal_spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,13 @@ import {
} from '../../primitives/signals';

describe('signals', () => {
it('should default to undefined when called with no argument', () => {
const s = signal<string>();
expect(s()).toBeUndefined();
s.set('hello');
expect(s()).toBe('hello');
});

it('should be a getter which reflects the set value', () => {
const state = signal(false);
expect(state()).toBeFalse();
Expand Down
Loading