From c9b5e24ceb4c690bce22db729523ff86653710d4 Mon Sep 17 00:00:00 2001
From: Thomas Kaul <4159106+dtslvr@users.noreply.github.com>
Date: Sun, 6 Sep 2026 20:08:41 +0200
Subject: [PATCH 1/2] Migrate create dialog to dedicated route
---
.../home-watchlist.component.ts | 66 ++--------
.../home-watchlist/home-watchlist.html | 8 +-
.../home-watchlist/home-watchlist.service.ts | 15 +++
.../watchlist-dialog-host.component.ts | 114 ++++++++++++++++++
.../src/app/pages/home/home-page.routes.ts | 9 ++
libs/common/src/lib/routes/routes.ts | 7 ++
6 files changed, 161 insertions(+), 58 deletions(-)
create mode 100644 apps/client/src/app/components/home-watchlist/home-watchlist.service.ts
create mode 100644 apps/client/src/app/components/home-watchlist/watchlist-dialog-host/watchlist-dialog-host.component.ts
diff --git a/apps/client/src/app/components/home-watchlist/home-watchlist.component.ts b/apps/client/src/app/components/home-watchlist/home-watchlist.component.ts
index d3a7fb203..b56d8d66e 100644
--- a/apps/client/src/app/components/home-watchlist/home-watchlist.component.ts
+++ b/apps/client/src/app/components/home-watchlist/home-watchlist.component.ts
@@ -6,6 +6,7 @@ import {
User
} from '@ghostfolio/common/interfaces';
import { hasPermission, permissions } from '@ghostfolio/common/permissions';
+import { internalRoutes } from '@ghostfolio/common/routes/routes';
import { hasScope, scopes } from '@ghostfolio/common/scopes';
import { GfBenchmarkComponent } from '@ghostfolio/ui/benchmark';
import { GfFabComponent } from '@ghostfolio/ui/fab';
@@ -23,12 +24,10 @@ import {
OnInit
} from '@angular/core';
import { takeUntilDestroyed } from '@angular/core/rxjs-interop';
-import { MatDialog } from '@angular/material/dialog';
-import { ActivatedRoute, Router, RouterModule } from '@angular/router';
+import { RouterModule } from '@angular/router';
import { DeviceDetectorService } from 'ngx-device-detector';
-import { GfCreateWatchlistItemDialogComponent } from './create-watchlist-item-dialog/create-watchlist-item-dialog.component';
-import { CreateWatchlistItemDialogParams } from './create-watchlist-item-dialog/interfaces/interfaces';
+import { HomeWatchlistService } from './home-watchlist.service';
@Component({
changeDetection: ChangeDetectionStrategy.OnPush,
@@ -38,6 +37,7 @@ import { CreateWatchlistItemDialogParams } from './create-watchlist-item-dialog/
GfPremiumIndicatorComponent,
RouterModule
],
+ providers: [HomeWatchlistService],
schemas: [CUSTOM_ELEMENTS_SCHEMA],
selector: 'gf-home-watchlist',
styleUrls: ['./home-watchlist.scss'],
@@ -45,6 +45,7 @@ import { CreateWatchlistItemDialogParams } from './create-watchlist-item-dialog/
})
export class GfHomeWatchlistComponent implements OnInit {
protected readonly DEFAULT_LOCALE = DEFAULT_LOCALE;
+ protected readonly internalRoutes = internalRoutes;
protected hasPermissionToCreateWatchlistItem: boolean;
protected hasPermissionToDeleteWatchlistItem: boolean;
@@ -59,18 +60,14 @@ export class GfHomeWatchlistComponent implements OnInit {
private readonly dataService = inject(DataService);
private readonly destroyRef = inject(DestroyRef);
private readonly deviceDetectorService = inject(DeviceDetectorService);
- private readonly dialog = inject(MatDialog);
- private readonly route = inject(ActivatedRoute);
- private readonly router = inject(Router);
+ private readonly homeWatchlistService = inject(HomeWatchlistService);
private readonly userService = inject(UserService);
public constructor() {
- this.route.queryParams
+ this.homeWatchlistService.refresh$
.pipe(takeUntilDestroyed(this.destroyRef))
- .subscribe((params) => {
- if (params['createWatchlistItemDialog']) {
- this.openCreateWatchlistItemDialog();
- }
+ .subscribe(() => {
+ this.loadWatchlistData();
});
this.userService.stateChanged
@@ -130,49 +127,4 @@ export class GfHomeWatchlistComponent implements OnInit {
}
});
}
-
- private openCreateWatchlistItemDialog() {
- this.userService
- .get()
- .pipe(takeUntilDestroyed(this.destroyRef))
- .subscribe((user) => {
- this.user = user;
-
- if (
- !hasPermission(user?.permissions, permissions.createWatchlistItem) ||
- !hasScope(user?.scopes, scopes.watchlistCreate)
- ) {
- this.router.navigate(['.'], { relativeTo: this.route });
-
- return;
- }
-
- const dialogRef = this.dialog.open<
- GfCreateWatchlistItemDialogComponent,
- CreateWatchlistItemDialogParams
- >(GfCreateWatchlistItemDialogComponent, {
- data: {
- deviceType: this.deviceType(),
- locale: this.user?.settings?.locale ?? DEFAULT_LOCALE
- },
- width: this.deviceType() === 'mobile' ? '100vw' : '50rem'
- });
-
- dialogRef
- .afterClosed()
- .pipe(takeUntilDestroyed(this.destroyRef))
- .subscribe(({ dataSource, symbol } = {}) => {
- if (dataSource && symbol) {
- this.dataService
- .postWatchlistItem({ dataSource, symbol })
- .pipe(takeUntilDestroyed(this.destroyRef))
- .subscribe({
- next: () => this.loadWatchlistData()
- });
- }
-
- this.router.navigate(['.'], { relativeTo: this.route });
- });
- });
- }
}
diff --git a/apps/client/src/app/components/home-watchlist/home-watchlist.html b/apps/client/src/app/components/home-watchlist/home-watchlist.html
index fdd894de4..11b2d9c2f 100644
--- a/apps/client/src/app/components/home-watchlist/home-watchlist.html
+++ b/apps/client/src/app/components/home-watchlist/home-watchlist.html
@@ -22,5 +22,11 @@
@if (hasPermissionToCreateWatchlistItem) {
-
+
}
+
+
diff --git a/apps/client/src/app/components/home-watchlist/home-watchlist.service.ts b/apps/client/src/app/components/home-watchlist/home-watchlist.service.ts
new file mode 100644
index 000000000..47eafe605
--- /dev/null
+++ b/apps/client/src/app/components/home-watchlist/home-watchlist.service.ts
@@ -0,0 +1,15 @@
+import { Service } from '@angular/core';
+import { Subject } from 'rxjs';
+
+@Service({ autoProvided: false })
+export class HomeWatchlistService {
+ private readonly refreshSubject = new Subject();
+
+ public get refresh$() {
+ return this.refreshSubject.asObservable();
+ }
+
+ public triggerRefresh() {
+ this.refreshSubject.next();
+ }
+}
diff --git a/apps/client/src/app/components/home-watchlist/watchlist-dialog-host/watchlist-dialog-host.component.ts b/apps/client/src/app/components/home-watchlist/watchlist-dialog-host/watchlist-dialog-host.component.ts
new file mode 100644
index 000000000..9e1887e27
--- /dev/null
+++ b/apps/client/src/app/components/home-watchlist/watchlist-dialog-host/watchlist-dialog-host.component.ts
@@ -0,0 +1,114 @@
+import { UserService } from '@ghostfolio/client/services/user/user.service';
+import { DEFAULT_LOCALE } from '@ghostfolio/common/config';
+import { User } from '@ghostfolio/common/interfaces';
+import { hasPermission, permissions } from '@ghostfolio/common/permissions';
+import { internalRoutes } from '@ghostfolio/common/routes/routes';
+import { hasScope, scopes } from '@ghostfolio/common/scopes';
+import { DataService } from '@ghostfolio/ui/services';
+
+import {
+ ChangeDetectionStrategy,
+ Component,
+ DestroyRef,
+ inject,
+ OnDestroy,
+ OnInit
+} from '@angular/core';
+import { takeUntilDestroyed } from '@angular/core/rxjs-interop';
+import { MatDialog, MatDialogRef } from '@angular/material/dialog';
+import { Router } from '@angular/router';
+import { DeviceDetectorService } from 'ngx-device-detector';
+
+import { GfCreateWatchlistItemDialogComponent } from '../create-watchlist-item-dialog/create-watchlist-item-dialog.component';
+import { CreateWatchlistItemDialogParams } from '../create-watchlist-item-dialog/interfaces/interfaces';
+import { HomeWatchlistService } from '../home-watchlist.service';
+
+@Component({
+ changeDetection: ChangeDetectionStrategy.OnPush,
+ selector: 'gf-watchlist-dialog-host',
+ template: ''
+})
+export class GfWatchlistDialogHostComponent implements OnDestroy, OnInit {
+ private dialogRef: MatDialogRef;
+
+ private readonly dataService = inject(DataService);
+ private readonly destroyRef = inject(DestroyRef);
+ private readonly deviceDetectorService = inject(DeviceDetectorService);
+ private readonly dialog = inject(MatDialog);
+ private readonly homeWatchlistService = inject(HomeWatchlistService);
+ private readonly router = inject(Router);
+ private readonly userService = inject(UserService);
+
+ public ngOnInit() {
+ this.userService
+ .get()
+ .pipe(takeUntilDestroyed(this.destroyRef))
+ .subscribe((user) => {
+ if (
+ !hasPermission(user?.permissions, permissions.createWatchlistItem) ||
+ !hasScope(user?.scopes, scopes.watchlistCreate)
+ ) {
+ this.navigateBack();
+
+ return;
+ }
+
+ this.openDialog(user);
+ });
+ }
+
+ public ngOnDestroy() {
+ // The dialog lives in an overlay outside of this component, so it needs to
+ // be closed explicitly when leaving the route (for example via the browser
+ // navigation)
+ this.dialogRef?.close();
+ }
+
+ private navigateBack() {
+ void this.router.navigate(
+ internalRoutes.home.subRoutes.watchlist.routerLink
+ );
+ }
+
+ private openDialog(user: User) {
+ const deviceType = this.deviceDetectorService.getDeviceInfo().deviceType;
+
+ const dialogRef = this.dialog.open<
+ GfCreateWatchlistItemDialogComponent,
+ CreateWatchlistItemDialogParams
+ >(GfCreateWatchlistItemDialogComponent, {
+ data: {
+ deviceType,
+ locale: user?.settings?.locale ?? DEFAULT_LOCALE
+ },
+ width: deviceType === 'mobile' ? '100vw' : '50rem'
+ });
+
+ this.dialogRef = dialogRef;
+
+ dialogRef
+ .afterClosed()
+ .pipe(takeUntilDestroyed(this.destroyRef))
+ .subscribe(({ dataSource, symbol } = {}) => {
+ if (!dataSource || !symbol) {
+ this.navigateBack();
+
+ return;
+ }
+
+ this.dataService
+ .postWatchlistItem({ dataSource, symbol })
+ .pipe(takeUntilDestroyed(this.destroyRef))
+ .subscribe({
+ error: () => {
+ this.navigateBack();
+ },
+ next: () => {
+ this.homeWatchlistService.triggerRefresh();
+
+ this.navigateBack();
+ }
+ });
+ });
+ }
+}
diff --git a/apps/client/src/app/pages/home/home-page.routes.ts b/apps/client/src/app/pages/home/home-page.routes.ts
index cc444c2b0..3e0ce7b26 100644
--- a/apps/client/src/app/pages/home/home-page.routes.ts
+++ b/apps/client/src/app/pages/home/home-page.routes.ts
@@ -2,6 +2,7 @@ import { GfHomeHoldingsComponent } from '@ghostfolio/client/components/home-hold
import { GfHomeOverviewComponent } from '@ghostfolio/client/components/home-overview/home-overview.component';
import { GfHomeSummaryComponent } from '@ghostfolio/client/components/home-summary/home-summary.component';
import { GfHomeWatchlistComponent } from '@ghostfolio/client/components/home-watchlist/home-watchlist.component';
+import { GfWatchlistDialogHostComponent } from '@ghostfolio/client/components/home-watchlist/watchlist-dialog-host/watchlist-dialog-host.component';
import { GfMarketsComponent } from '@ghostfolio/client/components/markets/markets.component';
import { AuthGuard } from '@ghostfolio/client/core/auth.guard';
import { internalRoutes } from '@ghostfolio/common/routes/routes';
@@ -34,6 +35,14 @@ export const routes: Routes = [
title: internalRoutes.home.subRoutes.markets.title
},
{
+ children: [
+ {
+ component: GfWatchlistDialogHostComponent,
+ path: internalRoutes.home.subRoutes.watchlist.subRoutes.create.path,
+ title:
+ internalRoutes.home.subRoutes.watchlist.subRoutes.create.title
+ }
+ ],
path: internalRoutes.home.subRoutes.watchlist.path,
component: GfHomeWatchlistComponent,
title: internalRoutes.home.subRoutes.watchlist.title
diff --git a/libs/common/src/lib/routes/routes.ts b/libs/common/src/lib/routes/routes.ts
index b45715607..ca3cdd366 100644
--- a/libs/common/src/lib/routes/routes.ts
+++ b/libs/common/src/lib/routes/routes.ts
@@ -156,6 +156,13 @@ export const internalRoutes = {
watchlist: {
path: 'watchlist',
routerLink: ['/home', 'watchlist'],
+ subRoutes: {
+ create: {
+ path: 'create',
+ routerLink: ['/home', 'watchlist', 'create'],
+ title: $localize`Add to Watchlist`
+ }
+ },
title: $localize`Watchlist`
}
},
From 9ef09d2e54d3be8f1452387b0bbd0a7347f71b96 Mon Sep 17 00:00:00 2001
From: Thomas Kaul <4159106+dtslvr@users.noreply.github.com>
Date: Sun, 6 Sep 2026 20:09:05 +0200
Subject: [PATCH 2/2] Update changelog
---
CHANGELOG.md | 6 ++++++
1 file changed, 6 insertions(+)
diff --git a/CHANGELOG.md b/CHANGELOG.md
index ed9b8f191..49e456f91 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -5,6 +5,12 @@ All notable changes to this project will be documented in this file.
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
+## Unreleased
+
+### Changed
+
+- Migrated the create dialog of the watchlist to a dedicated route
+
## 3.68.0 - 2026-09-06
### Added