Browse Source

Task/migrate services to Angular 22 @Service() (#7671)

* Migrate services to Angular 22 @Service()

* Update changelog

---------

Co-authored-by: Thomas Kaul <4159106+dtslvr@users.noreply.github.com>
pull/7675/head
Shubham Raghav 8 hours ago
committed by GitHub
parent
commit
5af0c7f1c0
No known key found for this signature in database GPG Key ID: B5690EEEBB952194
  1. 1
      CHANGELOG.md
  2. 3
      apps/client/src/app/adapter/custom-date-adapter.ts
  3. 10
      apps/client/src/app/components/admin-market-data/admin-market-data.service.ts
  4. 4
      apps/client/src/app/core/auth.guard.ts
  5. 4
      apps/client/src/app/core/auth.interceptor.ts
  6. 4
      apps/client/src/app/core/http-response.interceptor.ts
  7. 4
      apps/client/src/app/core/language.service.ts
  8. 18
      apps/client/src/app/core/layout.service.ts
  9. 4
      apps/client/src/app/core/module-preload.service.ts
  10. 8
      apps/client/src/app/services/cache.service.ts
  11. 6
      apps/client/src/app/services/ics/ics.service.ts
  12. 6
      apps/client/src/app/services/impersonation-storage.service.ts
  13. 6
      apps/client/src/app/services/import-activities.service.ts
  14. 8
      apps/client/src/app/services/page-title.strategy.ts
  15. 6
      apps/client/src/app/services/settings-storage.service.ts
  16. 6
      apps/client/src/app/services/token-storage.service.ts
  17. 6
      apps/client/src/app/services/user/user.service.ts
  18. 6
      apps/client/src/app/services/web-authn.service.ts
  19. 8
      apps/client/src/main.ts
  20. 9
      libs/ui/src/lib/entity-logo/entity-logo-image-source.service.ts
  21. 4
      libs/ui/src/lib/fire-calculator/fire-calculator.service.ts
  22. 4
      libs/ui/src/lib/notifications/notification.service.ts
  23. 6
      libs/ui/src/lib/services/admin.service.ts
  24. 6
      libs/ui/src/lib/services/data.service.ts

1
CHANGELOG.md

@ -10,6 +10,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
### Changed
- Improved the sorting to be case-insensitive in the account selector component
- Refactored the services to use the `@Service()` decorator of _Angular_
## 3.55.0 - 2026-08-19

3
apps/client/src/app/adapter/custom-date-adapter.ts

@ -1,9 +1,10 @@
import { getDateFormatString } from '@ghostfolio/common/helper';
import { inject } from '@angular/core';
import { inject, Service } from '@angular/core';
import { MAT_DATE_LOCALE, NativeDateAdapter } from '@angular/material/core';
import { addYears, format, getYear, parse } from 'date-fns';
@Service({ autoProvided: false })
export class CustomDateAdapter extends NativeDateAdapter {
public override locale = inject<string>(MAT_DATE_LOCALE);

10
apps/client/src/app/components/admin-market-data/admin-market-data.service.ts

@ -3,15 +3,13 @@ import { AssetProfileIdentifier } from '@ghostfolio/common/interfaces';
import { NotificationService } from '@ghostfolio/ui/notifications';
import { AdminService } from '@ghostfolio/ui/services';
import { Injectable } from '@angular/core';
import { inject, Service } from '@angular/core';
import { EMPTY, Subject, catchError, finalize, forkJoin } from 'rxjs';
@Injectable()
@Service({ autoProvided: false })
export class AdminMarketDataService {
public constructor(
private adminService: AdminService,
private notificationService: NotificationService
) {}
private readonly adminService = inject(AdminService);
private readonly notificationService = inject(NotificationService);
public deleteAssetProfile({ dataSource, symbol }: AssetProfileIdentifier) {
const assetProfileDeleted = new Subject<void>();

4
apps/client/src/app/core/auth.guard.ts

@ -3,7 +3,7 @@ import { UserService } from '@ghostfolio/client/services/user/user.service';
import { internalRoutes, publicRoutes } from '@ghostfolio/common/routes/routes';
import { DataService } from '@ghostfolio/ui/services';
import { inject, Injectable } from '@angular/core';
import { inject, Service } from '@angular/core';
import {
ActivatedRouteSnapshot,
Router,
@ -12,7 +12,7 @@ import {
import { EMPTY } from 'rxjs';
import { catchError } from 'rxjs/operators';
@Injectable({ providedIn: 'root' })
@Service()
export class AuthGuard {
private readonly dataService = inject(DataService);
private readonly router = inject(Router);

4
apps/client/src/app/core/auth.interceptor.ts

@ -13,10 +13,10 @@ import {
HttpInterceptor,
HttpRequest
} from '@angular/common/http';
import { inject, Injectable } from '@angular/core';
import { inject, Service } from '@angular/core';
import { Observable } from 'rxjs';
@Injectable()
@Service({ autoProvided: false })
export class AuthInterceptor implements HttpInterceptor {
private readonly impersonationStorageService = inject(
ImpersonationStorageService

4
apps/client/src/app/core/http-response.interceptor.ts

@ -14,7 +14,7 @@ import {
HttpInterceptor,
HttpRequest
} from '@angular/common/http';
import { inject, Injectable } from '@angular/core';
import { inject, Service } from '@angular/core';
import {
MatSnackBar,
MatSnackBarRef,
@ -26,7 +26,7 @@ import ms from 'ms';
import { Observable, throwError } from 'rxjs';
import { catchError } from 'rxjs/operators';
@Injectable()
@Service({ autoProvided: false })
export class HttpResponseInterceptor implements HttpInterceptor {
private readonly info: InfoItem;
private snackBarRef: MatSnackBarRef<TextOnlySnackBar> | undefined;

4
apps/client/src/app/core/language.service.ts

@ -1,4 +0,0 @@
import { Injectable } from '@angular/core';
@Injectable()
export class LanguageService {}

18
apps/client/src/app/core/layout.service.ts

@ -1,24 +1,22 @@
import { NotificationService } from '@ghostfolio/ui/notifications';
import { Injectable } from '@angular/core';
import { inject, Service } from '@angular/core';
import { DeviceDetectorService } from 'ngx-device-detector';
import { Observable, Subject } from 'rxjs';
@Injectable({
providedIn: 'root'
})
@Service()
export class LayoutService {
public static readonly DEFAULT_NOTIFICATION_MAX_WIDTH = '50rem';
public static readonly DEFAULT_NOTIFICATION_WIDTH = '75vw';
public shouldReloadContent$: Observable<void>;
public readonly shouldReloadContent$: Observable<void>;
private shouldReloadSubject = new Subject<void>();
private readonly shouldReloadSubject = new Subject<void>();
public constructor(
private deviceDetectorService: DeviceDetectorService,
private notificationService: NotificationService
) {
private readonly deviceDetectorService = inject(DeviceDetectorService);
private readonly notificationService = inject(NotificationService);
public constructor() {
this.shouldReloadContent$ = this.shouldReloadSubject.asObservable();
const deviceType = this.deviceDetectorService.getDeviceInfo().deviceType;

4
apps/client/src/app/core/module-preload.service.ts

@ -1,8 +1,8 @@
import { Injectable } from '@angular/core';
import { Service } from '@angular/core';
import { PreloadingStrategy, Route } from '@angular/router';
import { Observable, of } from 'rxjs';
@Injectable()
@Service({ autoProvided: false })
export class ModulePreloadService implements PreloadingStrategy {
/**
* Preloads all lazy loading modules with the attribute 'preload' set to true

8
apps/client/src/app/services/cache.service.ts

@ -1,11 +1,9 @@
import { HttpClient } from '@angular/common/http';
import { Injectable } from '@angular/core';
import { inject, Service } from '@angular/core';
@Injectable({
providedIn: 'root'
})
@Service()
export class CacheService {
public constructor(private http: HttpClient) {}
private readonly http = inject(HttpClient);
public flush() {
return this.http.post<any>(`/api/v1/cache/flush`, {});

6
apps/client/src/app/services/ics/ics.service.ts

@ -1,13 +1,11 @@
import { capitalize } from '@ghostfolio/common/helper';
import { ExportResponse } from '@ghostfolio/common/interfaces';
import { Injectable } from '@angular/core';
import { Service } from '@angular/core';
import { Type } from '@prisma/client';
import { format, parseISO } from 'date-fns';
@Injectable({
providedIn: 'root'
})
@Service()
export class IcsService {
private readonly ICS_DATE_FORMAT = 'yyyyMMdd';
private readonly ICS_LINE_BREAK = '\r\n';

6
apps/client/src/app/services/impersonation-storage.service.ts

@ -1,11 +1,9 @@
import { Injectable } from '@angular/core';
import { Service } from '@angular/core';
import { BehaviorSubject } from 'rxjs';
export const IMPERSONATION_KEY = 'impersonationId';
@Injectable({
providedIn: 'root'
})
@Service()
export class ImpersonationStorageService {
private hasImpersonationChangeSubject = new BehaviorSubject<string | null>(
this.getId()

6
apps/client/src/app/services/import-activities.service.ts

@ -13,16 +13,14 @@ import {
import { Activity } from '@ghostfolio/common/interfaces';
import { HttpClient } from '@angular/common/http';
import { inject, Injectable } from '@angular/core';
import { inject, Service } from '@angular/core';
import { Account, DataSource, Type as ActivityType } from '@prisma/client';
import { isFinite, isNumber, isString } from 'lodash';
import { parse as csvToJson } from 'papaparse';
import { firstValueFrom } from 'rxjs';
import { v4 as uuidv4 } from 'uuid';
@Injectable({
providedIn: 'root'
})
@Service()
export class ImportActivitiesService {
private static ACCOUNT_KEYS = ['account', 'accountid'];
private static COMMENT_KEYS = ['comment', 'note'];

8
apps/client/src/app/services/page-title.strategy.ts

@ -1,16 +1,14 @@
import { Injectable } from '@angular/core';
import { inject, Service } from '@angular/core';
import { Title } from '@angular/platform-browser';
import { RouterStateSnapshot, TitleStrategy } from '@angular/router';
@Injectable()
@Service({ autoProvided: false })
export class PageTitleStrategy extends TitleStrategy {
private static readonly DEFAULT_TITLE =
'Ghostfolio – Open Source Wealth Management Software';
private static readonly DEFAULT_TITLE_SHORT = 'Ghostfolio';
public constructor(private readonly title: Title) {
super();
}
private readonly title = inject(Title);
public override updateTitle(routerState: RouterStateSnapshot) {
const title = this.buildTitle(routerState);

6
apps/client/src/app/services/settings-storage.service.ts

@ -1,12 +1,10 @@
import { Injectable } from '@angular/core';
import { Service } from '@angular/core';
export const KEY_RANGE = 'range';
export const KEY_STAY_SIGNED_IN = 'staySignedIn';
export const KEY_TOKEN = 'auth-token';
@Injectable({
providedIn: 'root'
})
@Service()
export class SettingsStorageService {
public getSetting(aKey: string): string | null {
return window.localStorage.getItem(aKey);

6
apps/client/src/app/services/token-storage.service.ts

@ -1,10 +1,8 @@
import { Injectable } from '@angular/core';
import { Service } from '@angular/core';
import { KEY_TOKEN } from './settings-storage.service';
@Injectable({
providedIn: 'root'
})
@Service()
export class TokenStorageService {
public getToken(): string | null {
return (

6
apps/client/src/app/services/user/user.service.ts

@ -3,7 +3,7 @@ import { Filter, User } from '@ghostfolio/common/interfaces';
import { hasPermission, permissions } from '@ghostfolio/common/permissions';
import { HttpClient } from '@angular/common/http';
import { computed, DestroyRef, inject, Injectable } from '@angular/core';
import { computed, DestroyRef, inject, Service } from '@angular/core';
import { takeUntilDestroyed } from '@angular/core/rxjs-interop';
import { MatDialog } from '@angular/material/dialog';
import { ObservableStore } from '@codewithdan/observable-store';
@ -18,9 +18,7 @@ import { GfSubscriptionInterstitialDialogComponent } from '../../components/subs
import { UserStoreActions } from './user-store.actions';
import { UserStoreState } from './user-store.state';
@Injectable({
providedIn: 'root'
})
@Service()
export class UserService extends ObservableStore<UserStoreState> {
private readonly deviceType = computed(
() => this.deviceDetectorService.deviceInfo().deviceType

6
apps/client/src/app/services/web-authn.service.ts

@ -6,7 +6,7 @@ import {
} from '@ghostfolio/common/interfaces';
import { HttpClient } from '@angular/common/http';
import { inject, Injectable } from '@angular/core';
import { inject, Service } from '@angular/core';
import {
startAuthentication,
startRegistration
@ -14,9 +14,7 @@ import {
import { of } from 'rxjs';
import { catchError, switchMap, tap } from 'rxjs/operators';
@Injectable({
providedIn: 'root'
})
@Service()
export class WebAuthnService {
private static readonly WEB_AUTH_N_DEVICE_ID = 'WEB_AUTH_N_DEVICE_ID';

8
apps/client/src/main.ts

@ -4,7 +4,6 @@ import { registerChartConfiguration } from '@ghostfolio/ui/chart';
import { GF_ENVIRONMENT } from '@ghostfolio/ui/environment';
import { GfNotificationModule } from '@ghostfolio/ui/notifications';
import { Platform } from '@angular/cdk/platform';
import {
provideHttpClient,
withInterceptorsFromDi
@ -17,7 +16,6 @@ import {
import {
DateAdapter,
MAT_DATE_FORMATS,
MAT_DATE_LOCALE,
MatNativeDateModule
} from '@angular/material/core';
import { MatSnackBarModule } from '@angular/material/snack-bar';
@ -35,7 +33,6 @@ 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 { PageTitleStrategy } from './app/services/page-title.strategy';
import { environment } from './environments/environment';
@ -44,8 +41,7 @@ import { environment } from './environments/environment';
const response = await fetch('/api/v1/info');
const info: InfoResponse = await response.json();
const utmSource = window.localStorage.getItem('utm_source') as
| 'ios'
| 'trusted-web-activity';
'ios' | 'trusted-web-activity';
info.globalPermissions = filterGlobalPermissions(
info.globalPermissions,
@ -79,7 +75,6 @@ import { environment } from './environments/environment';
registrationStrategy: 'registerImmediately'
})
),
LanguageService,
ModulePreloadService,
provideHttpClient(withInterceptorsFromDi()),
provideIonicAngular(),
@ -87,7 +82,6 @@ import { environment } from './environments/environment';
provideNgxSkeletonLoader(),
provideZoneChangeDetection(),
{
deps: [LanguageService, MAT_DATE_LOCALE, Platform],
provide: DateAdapter,
useClass: CustomDateAdapter
},

9
libs/ui/src/lib/entity-logo/entity-logo-image-source.service.ts

@ -1,11 +1,10 @@
import { AssetProfileIdentifier } from '@ghostfolio/common/interfaces';
import { Injectable } from '@angular/core';
import { Service } from '@angular/core';
@Injectable({
// Required to allow mocking in Storybook
providedIn: 'root'
})
// Must stay auto-provided: several table stories render gf-entity-logo
// without providing an override and resolve this from the root injector
@Service()
export class EntityLogoImageSourceService {
public getLogoUrlByAssetProfileIdentifier({
dataSource,

4
libs/ui/src/lib/fire-calculator/fire-calculator.service.ts

@ -1,7 +1,7 @@
import { Injectable } from '@angular/core';
import { Service } from '@angular/core';
import { Big } from 'big.js';
@Injectable()
@Service({ autoProvided: false })
export class FireCalculatorService {
private readonly COMPOUND_PERIOD = 12;

4
libs/ui/src/lib/notifications/notification.service.ts

@ -1,7 +1,7 @@
import { ConfirmationDialogType } from '@ghostfolio/common/enums';
import { translate } from '@ghostfolio/ui/i18n';
import { inject, Injectable } from '@angular/core';
import { inject, Service } from '@angular/core';
import { MatDialog } from '@angular/material/dialog';
import { isFunction } from 'lodash';
@ -14,7 +14,7 @@ import {
} from './interfaces/interfaces';
import { GfPromptDialogComponent } from './prompt-dialog/prompt-dialog.component';
@Injectable()
@Service({ autoProvided: false })
export class NotificationService {
private dialogMaxWidth: string;
private dialogWidth: string;

6
libs/ui/src/lib/services/admin.service.ts

@ -24,14 +24,12 @@ import { DateRange } from '@ghostfolio/common/types';
import { GF_ENVIRONMENT } from '@ghostfolio/ui/environment';
import { HttpClient, HttpHeaders, HttpParams } from '@angular/common/http';
import { Injectable, inject } from '@angular/core';
import { inject, Service } from '@angular/core';
import { AssetProfileSplit, MarketData, Platform } from '@prisma/client';
import { JobStatus } from 'bull';
import { isNumber } from 'lodash';
@Injectable({
providedIn: 'root'
})
@Service()
export class AdminService {
private readonly environment = inject(GF_ENVIRONMENT);
private readonly http = inject(HttpClient);

6
libs/ui/src/lib/services/data.service.ts

@ -66,7 +66,7 @@ import type {
import { translate } from '@ghostfolio/ui/i18n';
import { HttpClient, HttpParams } from '@angular/common/http';
import { Injectable, inject } from '@angular/core';
import { inject, Service } from '@angular/core';
import { SortDirection } from '@angular/material/sort';
import { utc } from '@date-fns/utc';
import {
@ -85,9 +85,7 @@ import { cloneDeep, groupBy, isNumber } from 'lodash';
import { Observable } from 'rxjs';
import { map } from 'rxjs/operators';
@Injectable({
providedIn: 'root'
})
@Service()
export class DataService {
private readonly http = inject(HttpClient);

Loading…
Cancel
Save