(
source: Observable<T> | Subscribable<T>,
options?: ToSignalOptions<T | U> & {initialValue?: U},
)
| 126 | * @see [RxJS interop with Angular signals](ecosystem/rxjs-interop) |
| 127 | */ |
| 128 | export function toSignal<T, U = undefined>( |
| 129 | source: Observable<T> | Subscribable<T>, |
| 130 | options?: ToSignalOptions<T | U> & {initialValue?: U}, |
| 131 | ): Signal<T | U> { |
| 132 | typeof ngDevMode !== 'undefined' && |
| 133 | ngDevMode && |
| 134 | assertNotInReactiveContext( |
| 135 | toSignal, |
| 136 | 'Invoking `toSignal` causes new subscriptions every time. ' + |
| 137 | 'Consider moving `toSignal` outside of the reactive context and read the signal value where needed.', |
| 138 | ); |
| 139 | |
| 140 | const requiresCleanup = !options?.manualCleanup; |
| 141 | |
| 142 | if (ngDevMode && requiresCleanup && !options?.injector) { |
| 143 | assertInInjectionContext(toSignal); |
| 144 | } |
| 145 | |
| 146 | const cleanupRef = requiresCleanup |
| 147 | ? (options?.injector?.get(DestroyRef) ?? inject(DestroyRef)) |
| 148 | : null; |
| 149 | |
| 150 | const equal = makeToSignalEqual(options?.equal); |
| 151 | |
| 152 | // Note: T is the Observable value type, and U is the initial value type. They don't have to be |
| 153 | // the same - the returned signal gives values of type `T`. |
| 154 | let state: WritableSignal<State<T | U>>; |
| 155 | if (options?.requireSync) { |
| 156 | // Initially the signal is in a `NoValue` state. |
| 157 | state = signal( |
| 158 | {kind: StateKind.NoValue}, |
| 159 | {equal, ...(ngDevMode ? createDebugNameObject(options?.debugName, 'state') : undefined)}, |
| 160 | ); |
| 161 | } else { |
| 162 | // If an initial value was passed, use it. Otherwise, use `undefined` as the initial value. |
| 163 | state = signal<State<T | U>>( |
| 164 | {kind: StateKind.Value, value: options?.initialValue as U}, |
| 165 | {equal, ...(ngDevMode ? createDebugNameObject(options?.debugName, 'state') : undefined)}, |
| 166 | ); |
| 167 | } |
| 168 | |
| 169 | let destroyUnregisterFn: (() => void) | undefined; |
| 170 | |
| 171 | // Note: This code cannot run inside a reactive context (see assertion above). If we'd support |
| 172 | // this, we would subscribe to the observable outside of the current reactive context, avoiding |
| 173 | // that side-effect signal reads/writes are attribute to the current consumer. The current |
| 174 | // consumer only needs to be notified when the `state` signal changes through the observable |
| 175 | // subscription. Additional context (related to async pipe): |
| 176 | // https://github.com/angular/angular/pull/50522. |
| 177 | const sub = source.subscribe({ |
| 178 | next: (value) => state.set({kind: StateKind.Value, value}), |
| 179 | error: (error) => { |
| 180 | state.set({kind: StateKind.Error, error}); |
| 181 | destroyUnregisterFn?.(); |
| 182 | }, |
| 183 | complete: () => { |
| 184 | destroyUnregisterFn?.(); |
| 185 | }, |
no test coverage detected
searching dependent graphs…