Browse Source

feat: migrate services to Angular 22 @Service()

Replaces `@Injectable({ providedIn: 'root' })` with `@Service()` across the
13 services that used it. `@Service()` is automatically available in the DI
system, so the two are equivalent.

Two of them used constructor injection, which `@Service()` rejects with
NG2028, so those now inject their dependencies as fields -- the pattern the
rest of the codebase already uses. LayoutService keeps its constructor body;
only the parameters moved, and field initialisers still run before it.

Services declared with a bare `@Injectable()` are left alone: they are not
auto-provided, so they would need `@Service({ autoProvided: false })`, which
is a different change from the one this issue describes.

Resolves #7669

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
pull/7671/head
Shubham Raghav 1 week ago
parent
commit
1ccbb86a77
  1. 4
      apps/client/src/app/core/auth.guard.ts
  2. 13
      apps/client/src/app/core/layout.service.ts
  3. 8
      apps/client/src/app/services/cache.service.ts
  4. 6
      apps/client/src/app/services/ics/ics.service.ts
  5. 6
      apps/client/src/app/services/impersonation-storage.service.ts
  6. 6
      apps/client/src/app/services/import-activities.service.ts
  7. 6
      apps/client/src/app/services/settings-storage.service.ts
  8. 6
      apps/client/src/app/services/token-storage.service.ts
  9. 6
      apps/client/src/app/services/user/user.service.ts
  10. 6
      apps/client/src/app/services/web-authn.service.ts
  11. 6
      libs/ui/src/lib/entity-logo/entity-logo-image-source.service.ts
  12. 6
      libs/ui/src/lib/services/admin.service.ts
  13. 6
      libs/ui/src/lib/services/data.service.ts

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);

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

@ -1,24 +1,21 @@
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>;
private readonly deviceDetectorService = inject(DeviceDetectorService);
private readonly notificationService = inject(NotificationService);
private shouldReloadSubject = new Subject<void>();
public constructor(
private deviceDetectorService: DeviceDetectorService,
private notificationService: NotificationService
) {
public constructor() {
this.shouldReloadContent$ = this.shouldReloadSubject.asObservable();
const deviceType = this.deviceDetectorService.getDeviceInfo().deviceType;

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'];

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';

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

@ -1,11 +1,9 @@
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'
})
@Service()
export class EntityLogoImageSourceService {
public getLogoUrlByAssetProfileIdentifier({
dataSource,

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