From 45b21cada92eb26c56056470ba758886c95d68c6 Mon Sep 17 00:00:00 2001 From: David Requeno <108202767+DavidReque@users.noreply.github.com> Date: Wed, 5 Nov 2025 13:21:03 -0600 Subject: [PATCH] Task/migrate app component to standalone (#5906) * Migrate app component to standalone * Update changelog --- CHANGELOG.md | 1 + apps/client/src/app/app.component.ts | 18 ++-- apps/client/src/app/app.module.ts | 83 ----------------- .../{app-routing.module.ts => app.routes.ts} | 30 +----- apps/client/src/main.ts | 91 +++++++++++++++++-- 5 files changed, 97 insertions(+), 126 deletions(-) delete mode 100644 apps/client/src/app/app.module.ts rename apps/client/src/app/{app-routing.module.ts => app.routes.ts} (81%) diff --git a/CHANGELOG.md b/CHANGELOG.md index a73619a2f..6bbaba5ac 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -12,6 +12,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - Improved the _Self-Hosting_ section content for the _Compare with..._ concept on the Frequently Asked Questions (FAQ) page - Improved the _Self-Hosting_ section content for the _Markets_ concept on the Frequently Asked Questions (FAQ) page - Changed the build executor of the client from `@nx/angular:webpack-browser` to `@nx/angular:browser-esbuild` +- Refactored the app component to standalone - Improved the language localization for German (`de`) ### Fixed diff --git a/apps/client/src/app/app.component.ts b/apps/client/src/app/app.component.ts index 5ecb7bf8b..b70850016 100644 --- a/apps/client/src/app/app.component.ts +++ b/apps/client/src/app/app.component.ts @@ -1,5 +1,3 @@ -import { GfHoldingDetailDialogComponent } from '@ghostfolio/client/components/holding-detail-dialog/holding-detail-dialog.component'; -import { HoldingDetailDialogParams } from '@ghostfolio/client/components/holding-detail-dialog/interfaces/interfaces'; import { getCssVariable } from '@ghostfolio/common/helper'; import { InfoItem, User } from '@ghostfolio/common/interfaces'; import { hasPermission, permissions } from '@ghostfolio/common/permissions'; @@ -22,7 +20,9 @@ import { ActivatedRoute, NavigationEnd, PRIMARY_OUTLET, - Router + Router, + RouterLink, + RouterOutlet } from '@angular/router'; import { DataSource } from '@prisma/client'; import { addIcons } from 'ionicons'; @@ -31,6 +31,10 @@ import { DeviceDetectorService } from 'ngx-device-detector'; import { Subject } from 'rxjs'; import { filter, takeUntil } from 'rxjs/operators'; +import { GfFooterComponent } from './components/footer/footer.component'; +import { GfHeaderComponent } from './components/header/header.component'; +import { GfHoldingDetailDialogComponent } from './components/holding-detail-dialog/holding-detail-dialog.component'; +import { HoldingDetailDialogParams } from './components/holding-detail-dialog/interfaces/interfaces'; import { NotificationService } from './core/notification/notification.service'; import { DataService } from './services/data.service'; import { ImpersonationStorageService } from './services/impersonation-storage.service'; @@ -38,13 +42,13 @@ import { TokenStorageService } from './services/token-storage.service'; import { UserService } from './services/user/user.service'; @Component({ - selector: 'gf-root', changeDetection: ChangeDetectionStrategy.OnPush, - templateUrl: './app.component.html', + imports: [GfFooterComponent, GfHeaderComponent, RouterLink, RouterOutlet], + selector: 'gf-root', styleUrls: ['./app.component.scss'], - standalone: false + templateUrl: './app.component.html' }) -export class AppComponent implements OnDestroy, OnInit { +export class GfAppComponent implements OnDestroy, OnInit { @HostBinding('class.has-info-message') get getHasMessage() { return this.hasInfoMessage; } diff --git a/apps/client/src/app/app.module.ts b/apps/client/src/app/app.module.ts deleted file mode 100644 index 63de8fca7..000000000 --- a/apps/client/src/app/app.module.ts +++ /dev/null @@ -1,83 +0,0 @@ -import { Platform } from '@angular/cdk/platform'; -import { - provideHttpClient, - withInterceptorsFromDi -} from '@angular/common/http'; -import { CUSTOM_ELEMENTS_SCHEMA, NgModule } from '@angular/core'; -import { MatAutocompleteModule } from '@angular/material/autocomplete'; -import { MatChipsModule } from '@angular/material/chips'; -import { - DateAdapter, - MAT_DATE_FORMATS, - MAT_DATE_LOCALE, - MatNativeDateModule -} from '@angular/material/core'; -import { MatSnackBarModule } from '@angular/material/snack-bar'; -import { MatTooltipModule } from '@angular/material/tooltip'; -import { BrowserModule } from '@angular/platform-browser'; -import { BrowserAnimationsModule } from '@angular/platform-browser/animations'; -import { ServiceWorkerModule } from '@angular/service-worker'; -import { provideIonicAngular } from '@ionic/angular/standalone'; -import { provideMarkdown } from 'ngx-markdown'; -import { provideNgxSkeletonLoader } from 'ngx-skeleton-loader'; -import { NgxStripeModule, STRIPE_PUBLISHABLE_KEY } from 'ngx-stripe'; - -import { environment } from '../environments/environment'; -import { CustomDateAdapter } from './adapter/custom-date-adapter'; -import { DateFormats } from './adapter/date-formats'; -import { AppRoutingModule } from './app-routing.module'; -import { AppComponent } from './app.component'; -import { GfFooterComponent } from './components/footer/footer.component'; -import { GfHeaderComponent } from './components/header/header.component'; -import { authInterceptorProviders } from './core/auth.interceptor'; -import { httpResponseInterceptorProviders } from './core/http-response.interceptor'; -import { LanguageService } from './core/language.service'; -import { GfNotificationModule } from './core/notification/notification.module'; - -export function NgxStripeFactory(): string { - return environment.stripePublicKey; -} - -@NgModule({ - bootstrap: [AppComponent], - declarations: [AppComponent], - imports: [ - AppRoutingModule, - BrowserAnimationsModule, - BrowserModule, - GfFooterComponent, - GfHeaderComponent, - GfNotificationModule, - MatAutocompleteModule, - MatChipsModule, - MatNativeDateModule, - MatSnackBarModule, - MatTooltipModule, - NgxStripeModule.forRoot(environment.stripePublicKey), - ServiceWorkerModule.register('ngsw-worker.js', { - enabled: environment.production, - registrationStrategy: 'registerImmediately' - }) - ], - providers: [ - authInterceptorProviders, - httpResponseInterceptorProviders, - LanguageService, - provideHttpClient(withInterceptorsFromDi()), - provideIonicAngular(), - provideMarkdown(), - provideNgxSkeletonLoader(), - { - provide: DateAdapter, - useClass: CustomDateAdapter, - deps: [LanguageService, MAT_DATE_LOCALE, Platform] - }, - { provide: MAT_DATE_FORMATS, useValue: DateFormats }, - { - provide: STRIPE_PUBLISHABLE_KEY, - useFactory: NgxStripeFactory - } - ], - schemas: [CUSTOM_ELEMENTS_SCHEMA] -}) -export class AppModule {} diff --git a/apps/client/src/app/app-routing.module.ts b/apps/client/src/app/app.routes.ts similarity index 81% rename from apps/client/src/app/app-routing.module.ts rename to apps/client/src/app/app.routes.ts index fb045a174..9588cee68 100644 --- a/apps/client/src/app/app-routing.module.ts +++ b/apps/client/src/app/app.routes.ts @@ -1,13 +1,10 @@ -import { publicRoutes, internalRoutes } from '@ghostfolio/common/routes/routes'; +import { internalRoutes, publicRoutes } from '@ghostfolio/common/routes/routes'; -import { NgModule } from '@angular/core'; -import { RouterModule, Routes, TitleStrategy } from '@angular/router'; +import { Routes } from '@angular/router'; import { AuthGuard } from './core/auth.guard'; -import { ModulePreloadService } from './core/module-preload.service'; -import { PageTitleStrategy } from './services/page-title.strategy'; -const routes: Routes = [ +export const routes: Routes = [ { path: publicRoutes.about.path, loadChildren: () => @@ -147,24 +144,3 @@ const routes: Routes = [ pathMatch: 'full' } ]; - -@NgModule({ - imports: [ - RouterModule.forRoot( - routes, - // Preload all lazy loaded modules with the attribute preload === true - { - anchorScrolling: 'enabled', - // enableTracing: true, // <-- debugging purposes only - preloadingStrategy: ModulePreloadService, - scrollPositionRestoration: 'top' - } - ) - ], - providers: [ - ModulePreloadService, - { provide: TitleStrategy, useClass: PageTitleStrategy } - ], - exports: [RouterModule] -}) -export class AppRoutingModule {} diff --git a/apps/client/src/main.ts b/apps/client/src/main.ts index 96d6c0582..fc8a9ef7a 100644 --- a/apps/client/src/main.ts +++ b/apps/client/src/main.ts @@ -2,11 +2,39 @@ import { locale } from '@ghostfolio/common/config'; import { InfoResponse } from '@ghostfolio/common/interfaces'; import { filterGlobalPermissions } from '@ghostfolio/common/permissions'; -import { enableProdMode } from '@angular/core'; -import { LOCALE_ID } from '@angular/core'; -import { platformBrowserDynamic } from '@angular/platform-browser-dynamic'; +import { Platform } from '@angular/cdk/platform'; +import { + provideHttpClient, + withInterceptorsFromDi +} from '@angular/common/http'; +import { enableProdMode, importProvidersFrom, LOCALE_ID } from '@angular/core'; +import { + DateAdapter, + MAT_DATE_FORMATS, + MAT_DATE_LOCALE, + MatNativeDateModule +} from '@angular/material/core'; +import { MatSnackBarModule } from '@angular/material/snack-bar'; +import { MatTooltipModule } from '@angular/material/tooltip'; +import { bootstrapApplication } from '@angular/platform-browser'; +import { provideAnimations } from '@angular/platform-browser/animations'; +import { RouterModule, TitleStrategy } from '@angular/router'; +import { ServiceWorkerModule } from '@angular/service-worker'; +import { provideIonicAngular } from '@ionic/angular/standalone'; +import { provideMarkdown } from 'ngx-markdown'; +import { provideNgxSkeletonLoader } from 'ngx-skeleton-loader'; +import { NgxStripeModule, STRIPE_PUBLISHABLE_KEY } from 'ngx-stripe'; -import { AppModule } from './app/app.module'; +import { CustomDateAdapter } from './app/adapter/custom-date-adapter'; +import { DateFormats } from './app/adapter/date-formats'; +import { GfAppComponent } from './app/app.component'; +import { routes } from './app/app.routes'; +import { authInterceptorProviders } from './app/core/auth.interceptor'; +import { httpResponseInterceptorProviders } from './app/core/http-response.interceptor'; +import { LanguageService } from './app/core/language.service'; +import { ModulePreloadService } from './app/core/module-preload.service'; +import { GfNotificationModule } from './app/core/notification/notification.module'; +import { PageTitleStrategy } from './app/services/page-title.strategy'; import { environment } from './environments/environment'; (async () => { @@ -29,9 +57,54 @@ import { environment } from './environments/environment'; enableProdMode(); } - platformBrowserDynamic() - .bootstrapModule(AppModule, { - providers: [{ provide: LOCALE_ID, useValue: locale }] - }) - .catch((error) => console.error(error)); + await bootstrapApplication(GfAppComponent, { + providers: [ + authInterceptorProviders, + httpResponseInterceptorProviders, + importProvidersFrom( + GfNotificationModule, + MatNativeDateModule, + MatSnackBarModule, + MatTooltipModule, + NgxStripeModule.forRoot(environment.stripePublicKey), + RouterModule.forRoot(routes, { + anchorScrolling: 'enabled', + preloadingStrategy: ModulePreloadService, + scrollPositionRestoration: 'top' + }), + ServiceWorkerModule.register('ngsw-worker.js', { + enabled: environment.production, + registrationStrategy: 'registerImmediately' + }) + ), + LanguageService, + ModulePreloadService, + provideAnimations(), + provideHttpClient(withInterceptorsFromDi()), + provideIonicAngular(), + provideMarkdown(), + provideNgxSkeletonLoader(), + { + deps: [LanguageService, MAT_DATE_LOCALE, Platform], + provide: DateAdapter, + useClass: CustomDateAdapter + }, + { + provide: LOCALE_ID, + useValue: locale + }, + { + provide: MAT_DATE_FORMATS, + useValue: DateFormats + }, + { + provide: STRIPE_PUBLISHABLE_KEY, + useFactory: () => environment.stripePublicKey + }, + { + provide: TitleStrategy, + useClass: PageTitleStrategy + } + ] + }); })();