diff --git a/adev/shared-docs/components/search-dialog/search-dialog.component.ts b/adev/shared-docs/components/search-dialog/search-dialog.component.ts
index a1aea27fe71..bc4a02cc9eb 100644
--- a/adev/shared-docs/components/search-dialog/search-dialog.component.ts
+++ b/adev/shared-docs/components/search-dialog/search-dialog.component.ts
@@ -30,12 +30,12 @@ import {TextField} from '../text-field/text-field.component';
import {FormsModule} from '@angular/forms';
import {ActiveDescendantKeyManager} from '@angular/cdk/a11y';
import {SearchItem} from '../../directives/search-item/search-item.directive';
-import {takeUntilDestroyed} from '@angular/core/rxjs-interop';
import {Router, RouterLink} from '@angular/router';
-import {filter, fromEvent} from 'rxjs';
+import {fromEvent} from 'rxjs';
import {AlgoliaIcon} from '../algolia-icon/algolia-icon.component';
import {RelativeLink} from '../../pipes/relative-link.pipe';
import {SearchResult, SnippetResult} from '../../interfaces';
+import {takeUntilDestroyed} from '../../services/rxjs-interop';
@Component({
selector: 'docs-search-dialog',
diff --git a/adev/shared-docs/components/viewers/docs-viewer/docs-viewer.component.ts b/adev/shared-docs/components/viewers/docs-viewer/docs-viewer.component.ts
index 4c17dd08b5e..36c87e252e8 100644
--- a/adev/shared-docs/components/viewers/docs-viewer/docs-viewer.component.ts
+++ b/adev/shared-docs/components/viewers/docs-viewer/docs-viewer.component.ts
@@ -28,7 +28,6 @@ import {
ɵPendingTasksInternal as PendingTasks,
output,
} from '@angular/core';
-import {takeUntilDestroyed} from '@angular/core/rxjs-interop';
import {TOC_SKIP_CONTENT_MARKER, NavigationState} from '../../../services/index';
import {TableOfContents} from '../../table-of-contents/table-of-contents.component';
import {IconComponent} from '../../icon/icon.component';
@@ -40,6 +39,7 @@ import {fromEvent} from 'rxjs';
import {Breadcrumb} from '../../breadcrumb/breadcrumb.component';
import {CopySourceCodeButton} from '../../copy-source-code-button/copy-source-code-button.component';
import {ExampleViewer} from '../example-viewer/example-viewer.component';
+import {takeUntilDestroyed} from '../../../services/rxjs-interop';
///
diff --git a/adev/shared-docs/components/viewers/example-viewer/example-viewer.component.ts b/adev/shared-docs/components/viewers/example-viewer/example-viewer.component.ts
index 226b3cd52c2..e93dac718b2 100644
--- a/adev/shared-docs/components/viewers/example-viewer/example-viewer.component.ts
+++ b/adev/shared-docs/components/viewers/example-viewer/example-viewer.component.ts
@@ -29,8 +29,8 @@ import {Clipboard} from '@angular/cdk/clipboard';
import {CopySourceCodeButton} from '../../copy-source-code-button/copy-source-code-button.component';
import {ExampleMetadata, Snippet} from '../../../interfaces/index';
import {EXAMPLE_VIEWER_CONTENT_LOADER} from '../../../providers/index';
-import {takeUntilDestroyed} from '@angular/core/rxjs-interop';
import {DocViewer} from '../docs-viewer/docs-viewer.component';
+import {takeUntilDestroyed} from '../../../services/rxjs-interop';
export enum CodeExampleViewMode {
SNIPPET = 'snippet',
diff --git a/adev/shared-docs/services/BUILD.bazel b/adev/shared-docs/services/BUILD.bazel
index fec7953c401..639d3f3d03e 100644
--- a/adev/shared-docs/services/BUILD.bazel
+++ b/adev/shared-docs/services/BUILD.bazel
@@ -30,7 +30,6 @@ ng_module(
"//adev/shared-docs/utils",
"//packages/common",
"//packages/core",
- "//packages/core/rxjs-interop",
"//packages/router",
"@npm//algoliasearch",
"@npm//rxjs",
diff --git a/adev/shared-docs/services/rxjs-interop.ts b/adev/shared-docs/services/rxjs-interop.ts
new file mode 100644
index 00000000000..641cb656412
--- /dev/null
+++ b/adev/shared-docs/services/rxjs-interop.ts
@@ -0,0 +1,102 @@
+/**
+ * @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
+ */
+
+import {
+ assertInInjectionContext,
+ DestroyRef,
+ effect,
+ inject,
+ Injector,
+ Signal,
+ untracked,
+} from '@angular/core';
+import {Observable, ReplaySubject, MonoTypeOperatorFunction, takeUntil} from 'rxjs';
+
+// *******************************
+
+// TODO: Remove the file and revert the commit it belonged to in https://github.com/angular/angular/pull/60234
+// Once the next version of core package has been released
+
+// *******************************
+
+/**
+ * Operator which completes the Observable when the calling context (component, directive, service,
+ * etc) is destroyed.
+ *
+ * @param destroyRef optionally, the `DestroyRef` representing the current context. This can be
+ * passed explicitly to use `takeUntilDestroyed` outside of an [injection
+ * context](guide/di/dependency-injection-context). Otherwise, the current `DestroyRef` is injected.
+ *
+ * @publicApi
+ */
+export function takeUntilDestroyed(destroyRef?: DestroyRef): MonoTypeOperatorFunction {
+ if (!destroyRef) {
+ assertInInjectionContext(takeUntilDestroyed);
+ destroyRef = inject(DestroyRef);
+ }
+
+ const destroyed$ = new Observable((observer) => {
+ const unregisterFn = destroyRef!.onDestroy(observer.next.bind(observer));
+ return unregisterFn;
+ });
+
+ return (source: Observable) => {
+ return source.pipe(takeUntil(destroyed$));
+ };
+}
+
+/**
+ * Options for `toObservable`.
+ *
+ * @developerPreview
+ */
+export interface ToObservableOptions {
+ /**
+ * The `Injector` to use when creating the underlying `effect` which watches the signal.
+ *
+ * If this isn't specified, the current [injection context](guide/di/dependency-injection-context)
+ * will be used.
+ */
+ injector?: Injector;
+}
+
+/**
+ * Exposes the value of an Angular `Signal` as an RxJS `Observable`.
+ *
+ * The signal's value will be propagated into the `Observable`'s subscribers using an `effect`.
+ *
+ * `toObservable` must be called in an injection context unless an injector is provided via options.
+ *
+ * @developerPreview
+ */
+export function toObservable(source: Signal, options?: ToObservableOptions): Observable {
+ !options?.injector && assertInInjectionContext(toObservable);
+ const injector = options?.injector ?? inject(Injector);
+ const subject = new ReplaySubject(1);
+
+ const watcher = effect(
+ () => {
+ let value: T;
+ try {
+ value = source();
+ } catch (err) {
+ untracked(() => subject.error(err));
+ return;
+ }
+ untracked(() => subject.next(value));
+ },
+ {injector, manualCleanup: true},
+ );
+
+ injector.get(DestroyRef).onDestroy(() => {
+ watcher.destroy();
+ subject.complete();
+ });
+
+ return subject.asObservable();
+}
diff --git a/adev/shared-docs/services/search.service.ts b/adev/shared-docs/services/search.service.ts
index 4fa61f1279e..a11da1638df 100644
--- a/adev/shared-docs/services/search.service.ts
+++ b/adev/shared-docs/services/search.service.ts
@@ -9,10 +9,10 @@
import {Injectable, afterNextRender, inject, signal} from '@angular/core';
import {ENVIRONMENT} from '../providers/index';
import {SearchResult} from '../interfaces/index';
-import {toObservable} from '@angular/core/rxjs-interop';
import {debounceTime, filter, from, of, switchMap} from 'rxjs';
import {liteClient as algoliasearch} from 'algoliasearch/lite';
import {NavigationEnd, Router} from '@angular/router';
+import {toObservable} from './rxjs-interop';
export const SEARCH_DELAY = 200;
// Maximum number of facet values to return for each facet during a regular search.