Skip to content

Commit bc5ddab

Browse files
committed
feat(core): add Angular Signals to the public API (#49150)
This commit exposes `signal`, `computed`, `effect` and various helpers from the `@angular/core` entrypoint. These APIs are marked as `@developerPreview` and are still prototypes in active development. Their final shapes will be subject to our internal design reviews as well as one or more community RFCs. We're exporting them now to allow for experimentation using 16.0.0 next and RC releases. PR Close #49150
1 parent 5dce2a5 commit bc5ddab

File tree

7 files changed

+71
-10
lines changed

7 files changed

+71
-10
lines changed

goldens/public-api/core/index.md

Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -262,6 +262,9 @@ export abstract class ComponentRef<C> {
262262
abstract setInput(name: string, value: unknown): void;
263263
}
264264

265+
// @public
266+
export function computed<T>(computation: () => T, equal?: ValueEqualityFn<T>): Signal<T>;
267+
265268
// @public
266269
export interface ConstructorProvider extends ConstructorSansProvider {
267270
multi?: boolean;
@@ -475,6 +478,16 @@ export interface DoCheck {
475478
ngDoCheck(): void;
476479
}
477480

481+
// @public
482+
export interface Effect {
483+
readonly consumer: Consumer;
484+
destroy(): void;
485+
schedule(): void;
486+
}
487+
488+
// @public
489+
export function effect(effectFn: () => void): Effect;
490+
478491
// @public
479492
export class ElementRef<T = any> {
480493
constructor(nativeElement: T);
@@ -787,6 +800,9 @@ export interface InputDecorator {
787800
// @public
788801
export function isDevMode(): boolean;
789802

803+
// @public
804+
export function isSignal(value: Function): value is Signal<unknown>;
805+
790806
// @public
791807
export function isStandalone(type: Type<unknown>): boolean;
792808

@@ -1300,9 +1316,24 @@ export interface SelfDecorator {
13001316
new (): Self;
13011317
}
13021318

1319+
// @public
1320+
export interface SettableSignal<T> extends Signal<T> {
1321+
mutate(mutatorFn: (value: T) => void): void;
1322+
set(value: T): void;
1323+
update(updateFn: (value: T) => T): void;
1324+
}
1325+
13031326
// @public
13041327
export function setTestabilityGetter(getter: GetTestability): void;
13051328

1329+
// @public
1330+
export type Signal<T> = (() => T) & {
1331+
[SIGNAL]: true;
1332+
};
1333+
1334+
// @public
1335+
export function signal<T>(initialValue: T, equal?: ValueEqualityFn<T>): SettableSignal<T>;
1336+
13061337
// @public
13071338
export class SimpleChange {
13081339
constructor(previousValue: any, currentValue: any, firstChange: boolean);
@@ -1421,6 +1452,12 @@ export interface TypeDecorator {
14211452
export interface TypeProvider extends Type<any> {
14221453
}
14231454

1455+
// @public
1456+
export function untracked<T>(nonReactiveReadsFn: () => T): T;
1457+
1458+
// @public
1459+
export type ValueEqualityFn<T> = (a: T, b: T) => boolean;
1460+
14241461
// @public
14251462
export interface ValueProvider extends ValueSansProvider {
14261463
multi?: boolean;

packages/core/src/core.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,7 @@ export {EventEmitter} from './event_emitter';
3434
export {ErrorHandler} from './error_handler';
3535
export * from './core_private_export';
3636
export * from './core_render3_private_export';
37+
export * from './core_reactivity_export';
3738
export {SecurityContext} from './sanitization/security';
3839
export {Sanitizer} from './sanitization/sanitizer';
3940
export {createNgModule, createNgModuleRef, createEnvironmentInjector} from './render3/ng_module_ref';
Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
/**
2+
* @license
3+
* Copyright Google LLC All Rights Reserved.
4+
*
5+
* Use of this source code is governed by an MIT-style license that can be
6+
* found in the LICENSE file at https://angular.io/license
7+
*/
8+
9+
// This file exists to allow the set of reactivity exports to be modified in g3, as these APIs are
10+
// only "beta" for the time being.
11+
12+
export * from './core_reactivity_export_internal';
Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
/**
2+
* @license
3+
* Copyright Google LLC All Rights Reserved.
4+
*
5+
* Use of this source code is governed by an MIT-style license that can be
6+
* found in the LICENSE file at https://angular.io/license
7+
*/
8+
9+
export {computed, effect, Effect, isSignal, SettableSignal, Signal, signal, untracked, ValueEqualityFn} from './signals';

packages/core/src/signals/index.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -8,8 +8,8 @@
88

99
export {isSignal, Signal, ValueEqualityFn} from './src/api';
1010
export {computed} from './src/computed';
11-
export {effect} from './src/effect';
11+
export {effect, Effect} from './src/effect';
1212
export {setActiveConsumer} from './src/graph';
1313
export {SettableSignal, signal} from './src/signal';
14-
export {untracked as untrack} from './src/untracked';
14+
export {untracked} from './src/untracked';
1515
export {Watch} from './src/watch';

packages/core/src/signals/src/effect.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,8 @@ import {Watch} from './watch';
1111

1212
/**
1313
* A global reactive effect, which can be manually scheduled or destroyed.
14+
*
15+
* @developerPreview
1416
*/
1517
export interface Effect {
1618
/**

packages/core/test/signals/non_reactive_spec.ts

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

9-
import {computed, signal, untrack} from '@angular/core/src/signals';
9+
import {computed, signal, untracked} from '@angular/core/src/signals';
1010
import {effect, effectsDone as flush, resetEffects} from '@angular/core/src/signals/src/effect';
1111

1212
describe('non-reactive reads', () => {
@@ -17,15 +17,15 @@ describe('non-reactive reads', () => {
1717
it('should read the latest value from signal', () => {
1818
const counter = signal(0);
1919

20-
expect(untrack(counter)).toEqual(0);
20+
expect(untracked(counter)).toEqual(0);
2121

2222
counter.set(1);
23-
expect(untrack(counter)).toEqual(1);
23+
expect(untracked(counter)).toEqual(1);
2424
});
2525

2626
it('should not add dependencies to computed when reading a value from a signal', () => {
2727
const counter = signal(0);
28-
const double = computed(() => untrack(counter) * 2);
28+
const double = computed(() => untracked(counter) * 2);
2929

3030
expect(double()).toEqual(0);
3131

@@ -37,18 +37,18 @@ describe('non-reactive reads', () => {
3737
const counter = signal(0);
3838
const double = computed(() => counter() * 2);
3939

40-
expect(untrack(double)).toEqual(0);
40+
expect(untracked(double)).toEqual(0);
4141

4242
counter.set(2);
43-
expect(untrack(double)).toEqual(4);
43+
expect(untracked(double)).toEqual(4);
4444
});
4545

4646
it('should not make surrounding effect depend on the signal', async () => {
4747
const s = signal(1);
4848

4949
const runLog: number[] = [];
5050
effect(() => {
51-
runLog.push(untrack(s));
51+
runLog.push(untracked(s));
5252
});
5353

5454
// an effect will run at least once
@@ -89,7 +89,7 @@ describe('non-reactive reads', () => {
8989

9090
let runLog: string[] = [];
9191
const effectRef = effect(() => {
92-
untrack(() => runLog.push(`${first()} ${last()}`));
92+
untracked(() => runLog.push(`${first()} ${last()}`));
9393
});
9494

9595
// effects run at least once

0 commit comments

Comments
 (0)