Repository: angular
Issue: Signals - interoperability with non-angular libraries
Labels: area: core,core: reactivity,cross-cutting: signals

### Which @angular/* package(s) are relevant/related to the feature request?

core

### Description

I develop a library which is shared between server and client and does not depend on angular. I would like to use angulars signals in there.

One solution would be to separate signals from the rest of angular (like preact does it), but I don't think that this will happen for various reasons, which was also stated in the RFC if I remember correctly.

So my idea was to just copy the signals implementation, remove zone.js stuff in `effect()`, use `queueMicrotask()` instead of `ApplicationRef.tick()` as the scheduling. use that implementation for the server and the implementation in angular for the client. I actually did that to be able to use signals in angular v15 and it worked flawless (but of course without native support in templates).

In order for the signals created in the library code to work in angular and vice versa they must be created by angular.  My basic idea for that (not the actual code):
```ts
import * as Angular from '@angular/core';
import * as ExtractedSignals from './extracted-signals';

export let signal: signalFn = throwNotConfigured as any;
export let computed: computedFn = throwNotConfigured as any;

if (isAngular()) {
  signal = Angular.signal; // @angular/core signal
  computed = Angular.computed; // @angular/core computed
}
else {
  signal = ExtractedSignals.signal; // extracted signal
  computed = ExtractedSignals.computed; // extracted computed
}
```

The idea is to now import signal/computed etc. from that file and use that throughout the shared library and server code. When running in an angular app, it is technically the same (runtime behaviour) as if it was imported from `@angular/core`, but the types differ. When running outside an angular app it still works the same (except for effect scheduling) without depending on angular.

This seems to work well except for one part: types. The `SIGNAL` symbols from the two `Signal` types are obviously incompatible, as they are `unique symbol`s. So I can't pass signals from angular to the library and vice versa, even though they are identical at runtime.

Is there someone with an idea on how to solve this without changes to angular?

### Proposed solution

A simple solution would be to mark `[SIGNAL]` as optional. But this will probably have unwanted side-effects like allowing to pass plain functions to `Signal` parameters.

```ts
export type Signal<T> = (() => T) & {
    [SIGNAL]?: unknown;
};
```


Another solution would be to use an `interface` instead of a type to be able to use typescripts declaration merging.
Both `@angular/core` and the shared library export it this way (`SIGNAL` is not exported in the current version):
```ts
export const SIGNAL: unique symbol;

export interface Signal<T> {
  (): T;
  [SIGNAL]: unknown;
}
```


Consumer of angular and the shared library then merges them:
```ts
declare module 'shared-library' {
  interface Signal<T> {
    [AngularCore.SIGNAL]: unknown;
  }
}

declare module '@angular/core' {
  interface Signal<T> {
    [SharedLibrary.SIGNAL]: unknown;
  }
}
```

A thought that just appeared while I wrote that:
It actually would be enough if angular exports the `SIGNAL` symbol. Then I could merge one way, so that the libary `Signal` also have that symbol. But this way the library code would lack that extra bit of type safety if used standalone.

### Alternatives considered

Some `any`s in the glue-code and implicit type coercion, either using functions or types when passing library signals to angular and vice versa.

But this is out of question for me, as it's a bad DX and may reduces type safety.