diff --git a/devtools/projects/demo-standalone/BUILD.bazel b/devtools/projects/demo-standalone/BUILD.bazel
new file mode 100644
index 00000000000..ffd0fb0cdc5
--- /dev/null
+++ b/devtools/projects/demo-standalone/BUILD.bazel
@@ -0,0 +1 @@
+package(default_visibility = ["//visibility:public"])
diff --git a/devtools/projects/demo-standalone/src/BUILD.bazel b/devtools/projects/demo-standalone/src/BUILD.bazel
new file mode 100644
index 00000000000..76ff76d8810
--- /dev/null
+++ b/devtools/projects/demo-standalone/src/BUILD.bazel
@@ -0,0 +1,90 @@
+load("//devtools/tools:ng_module.bzl", "ng_module")
+load("@io_bazel_rules_sass//:defs.bzl", "sass_binary")
+load("@build_bazel_rules_nodejs//:index.bzl", "pkg_web")
+load("//tools:defaults.bzl", "esbuild", "http_server")
+load("//devtools/tools/esbuild:index.bzl", "LINKER_PROCESSED_FW_PACKAGES")
+
+package(default_visibility = ["//visibility:public"])
+
+sass_binary(
+ name = "demo_styles",
+ src = "styles.scss",
+ include_paths = ["external/npm/node_modules"],
+ sourcemap = False,
+ deps = ["//devtools:material_sass_deps"],
+)
+
+sass_binary(
+ name = "firefox_styles",
+ src = "styles/firefox_styles.scss",
+)
+
+sass_binary(
+ name = "chrome_styles",
+ src = "styles/chrome_styles.scss",
+)
+
+ng_module(
+ name = "demo",
+ srcs = ["main.ts"],
+ deps = [
+ "//devtools/projects/demo-standalone/src/app",
+ "//devtools/src:demo_application_environment",
+ "//devtools/src:demo_application_operations",
+ "//packages/common",
+ "//packages/common/http",
+ "//packages/core",
+ "//packages/core/src/util",
+ "//packages/platform-browser",
+ "@npm//@types",
+ "@npm//rxjs",
+ ],
+)
+
+esbuild(
+ name = "bundle",
+ config = "//devtools/tools/esbuild:esbuild_config_esm",
+ entry_points = [":main.ts"],
+ platform = "browser",
+ splitting = True,
+ target = "es2016",
+ deps = LINKER_PROCESSED_FW_PACKAGES + [":demo"],
+)
+
+exports_files(["index.html"])
+
+filegroup(
+ name = "dev_app_static_files",
+ srcs = [
+ ":chrome_styles",
+ ":demo_styles",
+ ":firefox_styles",
+ ":index.html",
+ "//packages/zone.js/bundles:zone.umd.js",
+ ],
+)
+
+pkg_web(
+ name = "devapp",
+ srcs = [":dev_app_static_files"] + [
+ ":bundle",
+ "@npm//:node_modules/tslib/tslib.js",
+ ],
+ # Currently, ibazel doesn't allow passing in flags to bazel run.
+ # This means we are not able to use --config snapshot-build
+ # to access the current commit SHA.
+ # Since we still want to be able to use ibazel to speed up
+ # local development, we supply an empty string for the SHA substitution.
+ # This does not effect production builds.
+ substitutions = {"BUILD_SCM_COMMIT_SHA": ""},
+)
+
+http_server(
+ name = "devserver",
+ srcs = [":dev_app_static_files"],
+ additional_root_paths = ["angular/devtools/projects/demo-standalone/src/devapp"],
+ tags = ["manual"],
+ deps = [
+ ":devapp",
+ ],
+)
diff --git a/devtools/projects/demo-standalone/src/app/BUILD.bazel b/devtools/projects/demo-standalone/src/app/BUILD.bazel
new file mode 100644
index 00000000000..087551ad399
--- /dev/null
+++ b/devtools/projects/demo-standalone/src/app/BUILD.bazel
@@ -0,0 +1,19 @@
+load("//devtools/tools:ng_module.bzl", "ng_module")
+
+package(default_visibility = ["//visibility:public"])
+
+ng_module(
+ name = "app",
+ srcs = [
+ "app.component.ts",
+ ],
+ deps = [
+ "//devtools/projects/demo-standalone/src/app/demo-app",
+ "//devtools/projects/demo-standalone/src/app/devtools-app",
+ "//devtools/projects/ng-devtools",
+ "//packages/core",
+ "//packages/platform-browser",
+ "//packages/platform-browser/animations",
+ "//packages/router",
+ ],
+)
diff --git a/devtools/projects/demo-standalone/src/app/app.component.ts b/devtools/projects/demo-standalone/src/app/app.component.ts
new file mode 100644
index 00000000000..f7724ead247
--- /dev/null
+++ b/devtools/projects/demo-standalone/src/app/app.component.ts
@@ -0,0 +1,20 @@
+/**
+ * @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.io/license
+ */
+
+import {Component} from '@angular/core';
+import {Router, RouterOutlet} from '@angular/router';
+
+@Component({
+ selector: 'app-root',
+ template: ``,
+ standalone: true,
+ imports: [RouterOutlet]
+})
+export class AppComponent {
+ constructor(public router: Router) {}
+}
diff --git a/devtools/projects/demo-standalone/src/app/demo-app/BUILD.bazel b/devtools/projects/demo-standalone/src/app/demo-app/BUILD.bazel
new file mode 100644
index 00000000000..5af1197e8d2
--- /dev/null
+++ b/devtools/projects/demo-standalone/src/app/demo-app/BUILD.bazel
@@ -0,0 +1,41 @@
+load("//devtools/tools:ng_module.bzl", "ng_module")
+load("@io_bazel_rules_sass//:defs.bzl", "sass_binary", "sass_library")
+
+package(default_visibility = ["//visibility:public"])
+
+sass_library(
+ name = "todo_mvc",
+ srcs = [
+ "@npm//:node_modules/todomvc-app-css/index.css",
+ "@npm//:node_modules/todomvc-common/base.css",
+ ],
+)
+
+sass_binary(
+ name = "demo_app_component_styles",
+ src = "demo-app.component.scss",
+ deps = [
+ ":todo_mvc",
+ ],
+)
+
+ng_module(
+ name = "demo-app",
+ srcs = [
+ "demo-app.component.ts",
+ "heavy.component.ts",
+ "zippy.component.ts",
+ ],
+ angular_assets = [
+ "demo-app.component.html",
+ ":demo_app_component_styles",
+ ],
+ deps = [
+ "//devtools/projects/demo-standalone/src/app/demo-app/todo",
+ "//devtools/projects/ng-devtools-backend",
+ "//devtools/src:zone-unaware-iframe_message_bus",
+ "//packages/core",
+ "//packages/elements",
+ "//packages/router",
+ ],
+)
diff --git a/devtools/projects/demo-standalone/src/app/demo-app/demo-app.component.html b/devtools/projects/demo-standalone/src/app/demo-app/demo-app.component.html
new file mode 100644
index 00000000000..36cc4c6bd2e
--- /dev/null
+++ b/devtools/projects/demo-standalone/src/app/demo-app/demo-app.component.html
@@ -0,0 +1,4 @@
+
+This is my content
+
+
HTMLElement
diff --git a/devtools/projects/demo-standalone/src/app/demo-app/demo-app.component.scss b/devtools/projects/demo-standalone/src/app/demo-app/demo-app.component.scss
new file mode 100644
index 00000000000..e281c4b6858
--- /dev/null
+++ b/devtools/projects/demo-standalone/src/app/demo-app/demo-app.component.scss
@@ -0,0 +1,2 @@
+@use 'external/npm/node_modules/todomvc-app-css/index.css';
+@use 'external/npm/node_modules/todomvc-common/base.css';
diff --git a/devtools/projects/demo-standalone/src/app/demo-app/demo-app.component.ts b/devtools/projects/demo-standalone/src/app/demo-app/demo-app.component.ts
new file mode 100644
index 00000000000..636a311c182
--- /dev/null
+++ b/devtools/projects/demo-standalone/src/app/demo-app/demo-app.component.ts
@@ -0,0 +1,65 @@
+/**
+ * @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.io/license
+ */
+
+import {Component, CUSTOM_ELEMENTS_SCHEMA, ElementRef, EventEmitter, inject, Injector, Input, Output, ViewChild, ViewEncapsulation} from '@angular/core';
+import {createCustomElement} from '@angular/elements';
+import {RouterOutlet} from '@angular/router';
+import {initializeMessageBus} from 'ng-devtools-backend';
+
+import {ZoneUnawareIFrameMessageBus} from '../../../../../src/zone-unaware-iframe-message-bus';
+
+import {HeavyComponent} from './heavy.component';
+import {ZippyComponent} from './zippy.component';
+
+@Component({
+ selector: 'app-demo-component',
+ templateUrl: './demo-app.component.html',
+ styleUrls: ['./demo-app.component.scss'],
+ encapsulation: ViewEncapsulation.None,
+ standalone: true,
+ imports: [HeavyComponent, RouterOutlet],
+ schemas: [CUSTOM_ELEMENTS_SCHEMA]
+})
+export class DemoAppComponent {
+ @ViewChild(ZippyComponent) zippy: ZippyComponent;
+ @ViewChild('elementReference') elementRef: ElementRef;
+
+ @Input('input_one') inputOne = 'input one';
+ @Input() inputTwo = 'input two';
+
+ @Output() outputOne = new EventEmitter();
+ @Output('output_two') outputTwo = new EventEmitter();
+
+ constructor() {
+ const el = createCustomElement(ZippyComponent, {injector: inject(Injector)});
+ customElements.define('app-zippy', el as any);
+ }
+
+ getTitle(): '► Click to expand'|'▼ Click to collapse' {
+ if (!this.zippy || !this.zippy.visible) {
+ return '► Click to expand';
+ }
+ return '▼ Click to collapse';
+ }
+}
+
+export const ROUTES = [
+ {
+ path: '',
+ component: DemoAppComponent,
+ children: [
+ {
+ path: '',
+ loadChildren: () => import('./todo/todo-app.component').then((m) => m.ROUTES),
+ },
+ ],
+ },
+];
+
+initializeMessageBus(new ZoneUnawareIFrameMessageBus(
+ 'angular-devtools-backend', 'angular-devtools', () => window.parent));
diff --git a/devtools/projects/demo-standalone/src/app/demo-app/heavy.component.ts b/devtools/projects/demo-standalone/src/app/demo-app/heavy.component.ts
new file mode 100644
index 00000000000..bca53cb54f1
--- /dev/null
+++ b/devtools/projects/demo-standalone/src/app/demo-app/heavy.component.ts
@@ -0,0 +1,43 @@
+/**
+ * @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.io/license
+ */
+
+import {Component, Input} from '@angular/core';
+
+const fib = (n: number) => {
+ if (n === 1 || n === 2) {
+ return 1;
+ }
+ return fib(n - 1) + fib(n - 2);
+};
+
+@Component({selector: 'app-heavy', template: `{{ calculate() }}
`, standalone: true})
+export class HeavyComponent {
+ @Input()
+ set foo(_: any) {
+ }
+
+ state = {
+ nested: {
+ props: {
+ foo: 1,
+ bar: 2,
+ },
+ [Symbol(3)]():
+ number {
+ return 1.618;
+ },
+ get foo():
+ number {
+ return 42;
+ },
+ },
+ };
+ calculate(): number {
+ return fib(15);
+ }
+}
diff --git a/devtools/projects/demo-standalone/src/app/demo-app/todo/BUILD.bazel b/devtools/projects/demo-standalone/src/app/demo-app/todo/BUILD.bazel
new file mode 100644
index 00000000000..10b72fee328
--- /dev/null
+++ b/devtools/projects/demo-standalone/src/app/demo-app/todo/BUILD.bazel
@@ -0,0 +1,20 @@
+load("//devtools/tools:ng_module.bzl", "ng_module")
+
+package(default_visibility = ["//visibility:public"])
+
+ng_module(
+ name = "todo",
+ srcs = [
+ "dialog.component.ts",
+ "todo-app.component.ts",
+ ],
+ deps = [
+ "//devtools/projects/demo-standalone/src/app/demo-app/todo/about",
+ "//devtools/projects/demo-standalone/src/app/demo-app/todo/home",
+ "//packages/common",
+ "//packages/core",
+ "//packages/forms",
+ "//packages/router",
+ "@npm//@angular/material",
+ ],
+)
diff --git a/devtools/projects/demo-standalone/src/app/demo-app/todo/about/BUILD.bazel b/devtools/projects/demo-standalone/src/app/demo-app/todo/about/BUILD.bazel
new file mode 100644
index 00000000000..e19d51b17c4
--- /dev/null
+++ b/devtools/projects/demo-standalone/src/app/demo-app/todo/about/BUILD.bazel
@@ -0,0 +1,12 @@
+load("//devtools/tools:ng_module.bzl", "ng_module")
+
+package(default_visibility = ["//visibility:public"])
+
+ng_module(
+ name = "about",
+ srcs = ["about.component.ts"],
+ deps = [
+ "//packages/core",
+ "//packages/router",
+ ],
+)
diff --git a/devtools/projects/demo-standalone/src/app/demo-app/todo/about/about.component.ts b/devtools/projects/demo-standalone/src/app/demo-app/todo/about/about.component.ts
new file mode 100644
index 00000000000..51a288972fa
--- /dev/null
+++ b/devtools/projects/demo-standalone/src/app/demo-app/todo/about/about.component.ts
@@ -0,0 +1,24 @@
+/**
+ * @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.io/license
+ */
+
+import {Component} from '@angular/core';
+import {RouterLink, RouterOutlet} from '@angular/router';
+
+@Component({
+ selector: 'app-about',
+ standalone: true,
+ imports: [RouterOutlet, RouterLink],
+ template: `
+ About component
+ Home
+ Home
+ Home
+ `
+})
+export class AboutComponent {
+}
diff --git a/devtools/projects/demo-standalone/src/app/demo-app/todo/dialog.component.ts b/devtools/projects/demo-standalone/src/app/demo-app/todo/dialog.component.ts
new file mode 100644
index 00000000000..c0cbda8eb65
--- /dev/null
+++ b/devtools/projects/demo-standalone/src/app/demo-app/todo/dialog.component.ts
@@ -0,0 +1,48 @@
+/**
+ * @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.io/license
+ */
+
+import {CommonModule} from '@angular/common';
+import {Component, Inject} from '@angular/core';
+import {FormsModule} from '@angular/forms';
+import {MAT_LEGACY_DIALOG_DATA as MAT_DIALOG_DATA, MatLegacyDialogModule as MatDialogModule, MatLegacyDialogRef as MatDialogRef} from '@angular/material/legacy-dialog';
+import {MatLegacyFormFieldModule as MatFormFieldModule} from '@angular/material/legacy-form-field';
+
+
+export interface DialogData {
+ animal: string;
+ name: string;
+}
+
+@Component({
+ selector: 'app-dialog',
+ standalone: true,
+ imports: [MatDialogModule, MatFormFieldModule, FormsModule, CommonModule],
+ template: `
+ Hi {{ data.name }}
+
+
What's your favorite animal?
+
+ Favorite Animal
+
+
+
+
+
+
+
+ `,
+})
+export class DialogComponent {
+ constructor(
+ public dialogRef: MatDialogRef,
+ @Inject(MAT_DIALOG_DATA) public data: DialogData) {}
+
+ onNoClick(): void {
+ this.dialogRef.close();
+ }
+}
diff --git a/devtools/projects/demo-standalone/src/app/demo-app/todo/home/BUILD.bazel b/devtools/projects/demo-standalone/src/app/demo-app/todo/home/BUILD.bazel
new file mode 100644
index 00000000000..9a470375245
--- /dev/null
+++ b/devtools/projects/demo-standalone/src/app/demo-app/todo/home/BUILD.bazel
@@ -0,0 +1,18 @@
+load("//devtools/tools:ng_module.bzl", "ng_module")
+
+package(default_visibility = ["//visibility:public"])
+
+ng_module(
+ name = "home",
+ srcs = [
+ "sample.pipe.ts",
+ "todo.component.ts",
+ "todos.component.ts",
+ "tooltip.directive.ts",
+ ],
+ deps = [
+ "//packages/common",
+ "//packages/core",
+ "//packages/router",
+ ],
+)
diff --git a/devtools/projects/demo-standalone/src/app/demo-app/todo/home/sample.pipe.ts b/devtools/projects/demo-standalone/src/app/demo-app/todo/home/sample.pipe.ts
new file mode 100644
index 00000000000..9b7ad7eef84
--- /dev/null
+++ b/devtools/projects/demo-standalone/src/app/demo-app/todo/home/sample.pipe.ts
@@ -0,0 +1,21 @@
+/**
+ * @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.io/license
+ */
+
+import {OnDestroy, Pipe, PipeTransform} from '@angular/core';
+
+@Pipe({name: 'sample', pure: false, standalone: true})
+export class SamplePipe implements PipeTransform, OnDestroy {
+ transform(val: any): void {
+ return val;
+ }
+
+ ngOnDestroy(): void {
+ // tslint:disable-next-line:no-console
+ console.log('Destroying');
+ }
+}
diff --git a/devtools/projects/demo-standalone/src/app/demo-app/todo/home/todo.component.ts b/devtools/projects/demo-standalone/src/app/demo-app/todo/home/todo.component.ts
new file mode 100644
index 00000000000..34c62bf47e9
--- /dev/null
+++ b/devtools/projects/demo-standalone/src/app/demo-app/todo/home/todo.component.ts
@@ -0,0 +1,68 @@
+/**
+ * @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.io/license
+ */
+
+import {CommonModule} from '@angular/common';
+import {ChangeDetectionStrategy, Component, EventEmitter, Input, Output} from '@angular/core';
+
+import {TooltipDirective} from './tooltip.directive';
+
+export interface Todo {
+ id: string;
+ completed: boolean;
+ label: string;
+}
+
+@Component({
+ selector: 'app-todo',
+ changeDetection: ChangeDetectionStrategy.OnPush,
+ standalone: true,
+ imports: [CommonModule, TooltipDirective],
+ styles: [`
+ .destroy {
+ cursor: pointer;
+ display: unset !important;
+ }
+ `],
+ template: `
+
+
+
+
+
+
+
+
+ `
+})
+export class TodoComponent {
+ @Input() todo: Todo;
+ @Output() update = new EventEmitter();
+ @Output() delete = new EventEmitter();
+
+ editMode = false;
+
+ toggle(): void {
+ this.todo.completed = !this.todo.completed;
+ this.update.emit(this.todo);
+ }
+
+ completeEdit(label: string): void {
+ this.todo.label = label;
+ this.editMode = false;
+ this.update.emit(this.todo);
+ }
+
+ enableEditMode(): void {
+ this.editMode = true;
+ }
+}
diff --git a/devtools/projects/demo-standalone/src/app/demo-app/todo/home/todos.component.ts b/devtools/projects/demo-standalone/src/app/demo-app/todo/home/todos.component.ts
new file mode 100644
index 00000000000..ffc009cf24e
--- /dev/null
+++ b/devtools/projects/demo-standalone/src/app/demo-app/todo/home/todos.component.ts
@@ -0,0 +1,165 @@
+/**
+ * @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.io/license
+ */
+
+import {NgForOf} from '@angular/common';
+import {ChangeDetectorRef, Component, EventEmitter, OnDestroy, OnInit, Output, Pipe, PipeTransform} from '@angular/core';
+import {RouterLink} from '@angular/router';
+
+import {SamplePipe} from './sample.pipe';
+import {Todo, TodoComponent} from './todo.component';
+import {TooltipDirective} from './tooltip.directive';
+
+export const enum TodoFilter {
+ All = 'all',
+ Completed = 'completed',
+ Active = 'active',
+}
+
+@Pipe({pure: false, name: 'todosFilter', standalone: true})
+export class TodosFilter implements PipeTransform {
+ transform(todos: Todo[], filter: TodoFilter): Todo[] {
+ return (todos || []).filter((t) => {
+ if (filter === TodoFilter.All) {
+ return true;
+ }
+ if (filter === TodoFilter.Active && !t.completed) {
+ return true;
+ }
+ if (filter === TodoFilter.Completed && t.completed) {
+ return true;
+ }
+ return false;
+ });
+ }
+}
+
+const fib = (n: number) => {
+ if (n === 1 || n === 2) {
+ return 1;
+ }
+ return fib(n - 1) + fib(n - 2);
+};
+
+@Component({
+ selector: 'app-todos',
+ imports: [NgForOf, RouterLink, TodoComponent, SamplePipe, TodosFilter, TooltipDirective],
+ standalone: true,
+ template: `
+ Home
+ Home
+ Home
+
+ {{ 'Sample text processed by a pipe' | sample }}
+
+
+ `
+})
+export class TodosComponent implements OnInit, OnDestroy {
+ todos: Todo[] = [
+ {
+ label: 'Buy milk',
+ completed: false,
+ id: '42',
+ },
+ {
+ label: 'Build something fun!',
+ completed: false,
+ id: '43',
+ },
+ ];
+
+ @Output() update = new EventEmitter();
+ @Output() delete = new EventEmitter();
+ @Output() add = new EventEmitter();
+
+ private hashListener: EventListenerOrEventListenerObject;
+
+ constructor(private cdRef: ChangeDetectorRef) {}
+
+ ngOnInit(): void {
+ if (typeof window !== 'undefined') {
+ window.addEventListener('hashchange', (this.hashListener = () => this.cdRef.markForCheck()));
+ }
+ }
+
+ ngOnDestroy(): void {
+ fib(40);
+ if (typeof window !== 'undefined') {
+ window.removeEventListener('hashchange', this.hashListener);
+ }
+ }
+
+ get filterValue(): TodoFilter {
+ if (typeof window !== 'undefined') {
+ return (window.location.hash.replace(/^#\//, '') as TodoFilter) || TodoFilter.All;
+ }
+ return TodoFilter.All;
+ }
+
+ get itemsLeft(): number {
+ return (this.todos || []).filter((t) => !t.completed).length;
+ }
+
+ clearCompleted(): void {
+ (this.todos || []).filter((t) => t.completed).forEach((t) => this.delete.emit(t));
+ }
+
+ addTodo(input: HTMLInputElement): void {
+ const todo = {
+ completed: false,
+ label: input.value,
+ };
+ const result: Todo = {...todo, id: Math.random().toString()};
+ this.todos.push(result);
+ input.value = '';
+ }
+
+ onChange(todo: Todo): void {
+ if (!todo.id) {
+ return;
+ }
+ }
+
+ onDelete(todo: Todo): void {
+ if (!todo.id) {
+ return;
+ }
+ const idx = this.todos.findIndex((t) => t.id === todo.id);
+ if (idx < 0) {
+ return;
+ }
+ // tslint:disable-next-line:no-console
+ console.log('Deleting', idx);
+
+ this.todos.splice(idx, 1);
+ }
+}
diff --git a/devtools/projects/demo-standalone/src/app/demo-app/todo/home/tooltip.directive.ts b/devtools/projects/demo-standalone/src/app/demo-app/todo/home/tooltip.directive.ts
new file mode 100644
index 00000000000..f5b5ec29c19
--- /dev/null
+++ b/devtools/projects/demo-standalone/src/app/demo-app/todo/home/tooltip.directive.ts
@@ -0,0 +1,35 @@
+/**
+ * @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.io/license
+ */
+
+import {Directive, HostListener} from '@angular/core';
+
+@Directive({selector: '[appTooltip]', standalone: true})
+export class TooltipDirective {
+ visible = false;
+ nested = {
+ child: {
+ grandchild: {
+ prop: 1,
+ },
+ },
+ };
+
+ constructor() {
+ // setInterval(() => this.nested.child.grandchild.prop++, 500);
+ }
+
+ @HostListener('click')
+ handleClick(): void {
+ this.visible = !this.visible;
+ if (this.visible) {
+ (this as any).extraProp = true;
+ } else {
+ delete (this as any).extraProp;
+ }
+ }
+}
diff --git a/devtools/projects/demo-standalone/src/app/demo-app/todo/todo-app.component.ts b/devtools/projects/demo-standalone/src/app/demo-app/todo/todo-app.component.ts
new file mode 100644
index 00000000000..1ab429e754d
--- /dev/null
+++ b/devtools/projects/demo-standalone/src/app/demo-app/todo/todo-app.component.ts
@@ -0,0 +1,98 @@
+/**
+ * @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.io/license
+ */
+
+import {CommonModule} from '@angular/common';
+import {Component} from '@angular/core';
+import {FormsModule} from '@angular/forms';
+import {MatLegacyDialog as MatDialog, MatLegacyDialogModule as MatDialogModule} from '@angular/material/legacy-dialog';
+import {MatLegacyFormFieldModule as MatFormFieldModule} from '@angular/material/legacy-form-field';
+import {MatLegacyInputModule as MatInputModule} from '@angular/material/legacy-input';
+import {RouterLink, RouterOutlet} from '@angular/router';
+
+import {DialogComponent} from './dialog.component';
+
+@Component({
+ selector: 'app-todo-demo',
+ standalone: true,
+ imports: [
+ RouterLink, RouterOutlet, MatDialogModule, MatFormFieldModule, MatInputModule, FormsModule,
+ CommonModule
+ ],
+ styles: [`
+ nav {
+ padding-top: 20px;
+ padding-bottom: 10px;
+
+ a {
+ margin-right: 15px;
+ text-decoration: none;
+ }
+ }
+
+ .dialog-open-button {
+ border: 1px solid #ccc;
+ padding: 10px;
+ margin-right: 20px;
+ }
+ `],
+ template: `
+
+
+
+
+
+ `
+})
+export class TodoAppComponent {
+ name: string;
+ animal: string;
+
+ constructor(public dialog: MatDialog) {}
+
+ openDialog(): void {
+ const dialogRef = this.dialog.open(DialogComponent, {
+ width: '250px',
+ data: {name: this.name, animal: this.animal},
+ });
+
+ dialogRef.afterClosed().subscribe((result) => {
+ // tslint:disable-next-line:no-console
+ console.log('The dialog was closed');
+
+ this.animal = result;
+ });
+ }
+}
+
+export const ROUTES = [
+ {
+ path: 'todos',
+ component: TodoAppComponent,
+ children: [
+ {
+ path: 'app',
+ loadComponent: () => import('./home/todos.component').then((m) => m.TodosComponent),
+ },
+ {
+ path: 'about',
+ loadComponent: () => import('./about/about.component').then((m) => m.AboutComponent),
+ },
+ {
+ path: '**',
+ redirectTo: 'app',
+ },
+ ],
+ },
+ {
+ path: '**',
+ redirectTo: 'todos',
+ },
+];
diff --git a/devtools/projects/demo-standalone/src/app/demo-app/zippy.component.ts b/devtools/projects/demo-standalone/src/app/demo-app/zippy.component.ts
new file mode 100644
index 00000000000..c01a08d9ed8
--- /dev/null
+++ b/devtools/projects/demo-standalone/src/app/demo-app/zippy.component.ts
@@ -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.io/license
+ */
+
+import {Component, Input} from '@angular/core';
+
+@Component({
+ selector: 'app-zippy',
+ standalone: true,
+ styles: [`
+ :host {
+ user-select: none;
+ }
+
+ header {
+ max-width: 120px;
+ border: 1px solid #ccc;
+ padding-left: 10px;
+ padding-right: 10px;
+ cursor: pointer;
+ }
+
+ div {
+ max-width: 120px;
+ border-left: 1px solid #ccc;
+ border-right: 1px solid #ccc;
+ border-bottom: 1px solid #ccc;
+ padding: 10px;
+ }
+ `],
+ template: `
+
+ `
+})
+export class ZippyComponent {
+ @Input() title: string;
+ visible = false;
+}
diff --git a/devtools/projects/demo-standalone/src/app/devtools-app/BUILD.bazel b/devtools/projects/demo-standalone/src/app/devtools-app/BUILD.bazel
new file mode 100644
index 00000000000..8f5be35470b
--- /dev/null
+++ b/devtools/projects/demo-standalone/src/app/devtools-app/BUILD.bazel
@@ -0,0 +1,16 @@
+load("//devtools/tools:ng_module.bzl", "ng_module")
+
+package(default_visibility = ["//visibility:public"])
+
+ng_module(
+ name = "devtools-app",
+ srcs = ["devtools-app.component.ts"],
+ deps = [
+ "//devtools/projects/ng-devtools",
+ "//devtools/projects/protocol",
+ "//devtools/src:iframe_message_bus",
+ "//packages/common",
+ "//packages/core",
+ "//packages/router",
+ ],
+)
diff --git a/devtools/projects/demo-standalone/src/app/devtools-app/devtools-app.component.ts b/devtools/projects/demo-standalone/src/app/devtools-app/devtools-app.component.ts
new file mode 100644
index 00000000000..912c699c6ca
--- /dev/null
+++ b/devtools/projects/demo-standalone/src/app/devtools-app/devtools-app.component.ts
@@ -0,0 +1,51 @@
+/**
+ * @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.io/license
+ */
+
+import {Component, ElementRef, ViewChild} from '@angular/core';
+import {DevToolsModule as NgDevToolsModule} from 'ng-devtools';
+import {Events, MessageBus, PriorityAwareMessageBus} from 'protocol';
+
+import {IFrameMessageBus} from '../../../../../src/iframe-message-bus';
+
+@Component({
+ standalone: true,
+ imports: [NgDevToolsModule],
+ providers: [
+ {
+ provide: MessageBus,
+ useFactory(): MessageBus {
+ return new PriorityAwareMessageBus(new IFrameMessageBus(
+ 'angular-devtools', 'angular-devtools-backend',
+ // tslint:disable-next-line: no-non-null-assertion
+ () => (document.querySelector('#sample-app') as HTMLIFrameElement).contentWindow!));
+ },
+ },
+ ],
+ styles: [`
+ iframe {
+ height: 340px;
+ width: 100%;
+ border: 0;
+ }
+
+ .devtools-wrapper {
+ height: calc(100vh - 345px);
+ }
+ `],
+ template: `
+
+
+
+
+
+ `
+})
+export class DevToolsComponent {
+ messageBus: IFrameMessageBus|null = null;
+ @ViewChild('ref') iframe: ElementRef;
+}
diff --git a/devtools/projects/demo-standalone/src/assets/.gitkeep b/devtools/projects/demo-standalone/src/assets/.gitkeep
new file mode 100644
index 00000000000..e69de29bb2d
diff --git a/devtools/projects/demo-standalone/src/environments/BUILD.bazel b/devtools/projects/demo-standalone/src/environments/BUILD.bazel
new file mode 100644
index 00000000000..47a65ebf603
--- /dev/null
+++ b/devtools/projects/demo-standalone/src/environments/BUILD.bazel
@@ -0,0 +1,13 @@
+load("//devtools/tools:typescript.bzl", "ts_library")
+
+package(default_visibility = ["//visibility:public"])
+
+ts_library(
+ name = "environments",
+ srcs = [
+ "environment.ts",
+ ],
+ deps = [
+ "//packages/platform-browser",
+ ],
+)
diff --git a/devtools/projects/demo-standalone/src/environments/environment.ts b/devtools/projects/demo-standalone/src/environments/environment.ts
new file mode 100644
index 00000000000..6830d2daa76
--- /dev/null
+++ b/devtools/projects/demo-standalone/src/environments/environment.ts
@@ -0,0 +1,12 @@
+/**
+ * @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.io/license
+ */
+
+export const environment = {
+ production: false,
+ LATEST_SHA: 'BUILD_SCM_COMMIT_SHA', // Stamped at build time by bazel
+};
diff --git a/devtools/projects/demo-standalone/src/index.html b/devtools/projects/demo-standalone/src/index.html
new file mode 100644
index 00000000000..9e37f92c6a5
--- /dev/null
+++ b/devtools/projects/demo-standalone/src/index.html
@@ -0,0 +1,21 @@
+
+
+
+
+ AngularDevtools
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/devtools/projects/demo-standalone/src/main.ts b/devtools/projects/demo-standalone/src/main.ts
new file mode 100644
index 00000000000..353cacd0108
--- /dev/null
+++ b/devtools/projects/demo-standalone/src/main.ts
@@ -0,0 +1,44 @@
+/**
+ * @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.io/license
+ */
+
+import {bootstrapApplication} from '@angular/platform-browser';
+import {provideAnimations} from '@angular/platform-browser/animations';
+import {provideRouter} from '@angular/router';
+import {ApplicationEnvironment, ApplicationOperations} from 'ng-devtools';
+
+import {DemoApplicationEnvironment} from '../../../src/demo-application-environment';
+import {DemoApplicationOperations} from '../../../src/demo-application-operations';
+
+import {AppComponent} from './app/app.component';
+
+
+bootstrapApplication(AppComponent, {
+ providers: [
+ provideAnimations(),
+ provideRouter([
+ {
+ path: '',
+ loadComponent: () =>
+ import('./app/devtools-app/devtools-app.component').then((m) => m.DevToolsComponent),
+ pathMatch: 'full',
+ },
+ {
+ path: 'demo-app',
+ loadChildren: () => import('./app/demo-app/demo-app.component').then((m) => m.ROUTES),
+ },
+ ]),
+ {
+ provide: ApplicationOperations,
+ useClass: DemoApplicationOperations,
+ },
+ {
+ provide: ApplicationEnvironment,
+ useClass: DemoApplicationEnvironment,
+ },
+ ]
+});
diff --git a/devtools/projects/demo-standalone/src/styles.scss b/devtools/projects/demo-standalone/src/styles.scss
new file mode 100644
index 00000000000..bb87d3e30fd
--- /dev/null
+++ b/devtools/projects/demo-standalone/src/styles.scss
@@ -0,0 +1,48 @@
+@use 'sass:map';
+@use 'external/npm/node_modules/@angular/material/index' as mat;
+@include mat.all-legacy-component-typographies();
+@include mat.legacy-core();
+
+html,
+body {
+ padding: 0;
+ margin: 0;
+}
+
+// Light theme
+$light-primary: mat.define-palette(mat.$grey-palette, 700, 200);
+$light-accent: mat.define-palette(mat.$blue-palette, 800);
+$light-theme: mat.define-light-theme($light-primary, $light-accent);
+
+// Dark theme
+$dark-primary: mat.define-palette(mat.$blue-grey-palette, 50);
+$dark-accent: mat.define-palette(mat.$blue-palette, 200);
+$dark-theme: map.deep-merge(
+ mat.define-dark-theme($dark-primary, $dark-accent),
+ (
+ 'color': (
+ 'background': (
+ background: #202124,
+ card: #202124,
+ ),
+ 'foreground': (
+ text: #bcc5ce,
+ ),
+ ),
+ 'background': (
+ background: #202124,
+ card: #202124,
+ ),
+ 'foreground': (
+ 'text': #bcc5ce,
+ ),
+ )
+);
+
+.light-theme {
+ @include mat.all-legacy-component-themes($light-theme);
+}
+
+.dark-theme {
+ @include mat.all-legacy-component-themes($dark-theme);
+}
diff --git a/devtools/projects/demo-standalone/src/styles/chrome_styles.scss b/devtools/projects/demo-standalone/src/styles/chrome_styles.scss
new file mode 100644
index 00000000000..f7b37f49000
--- /dev/null
+++ b/devtools/projects/demo-standalone/src/styles/chrome_styles.scss
@@ -0,0 +1,2 @@
+/** Specific style for Chrome browser */
+
diff --git a/devtools/projects/demo-standalone/src/styles/firefox_styles.scss b/devtools/projects/demo-standalone/src/styles/firefox_styles.scss
new file mode 100644
index 00000000000..824bf3f4bb1
--- /dev/null
+++ b/devtools/projects/demo-standalone/src/styles/firefox_styles.scss
@@ -0,0 +1,2 @@
+/** Specific style for Firefox browser */
+
diff --git a/devtools/src/index.html b/devtools/src/index.html
index c397bc74d1f..9e37f92c6a5 100644
--- a/devtools/src/index.html
+++ b/devtools/src/index.html
@@ -8,7 +8,6 @@
-
diff --git a/package.json b/package.json
index a3e30579641..b91597222a3 100644
--- a/package.json
+++ b/package.json
@@ -39,6 +39,7 @@
"ts-circular-deps:approve": "yarn -s ng-dev ts-circular-deps approve --config ./packages/circular-deps-test.conf.js",
"check-tooling-setup": "yarn tsc --project .ng-dev/tsconfig.json && yarn tsc --project scripts/tsconfig.json",
"devtools:devserver": "ibazel run //devtools/src:devserver",
+ "devtools:devserver:demo-standalone": "ibazel run //devtools/projects/demo-standalone/src:devserver",
"devtools:build:chrome": "bazelisk build --config snapshot-build --//devtools/projects/shell-browser/src:flag_browser=chrome -- devtools/projects/shell-browser/src:prodapp",
"devtools:build:firefox": "bazelisk build --config snapshot-build --//devtools/projects/shell-browser/src:flag_browser=firefox -- devtools/projects/shell-browser/src:prodapp",
"devtools:test": "bazelisk test --config snapshot-build --//devtools/projects/shell-browser/src:flag_browser=chrome -- //devtools/..."