Skip to content

Commit

Permalink
refactor: use field initializers for injectees (#2258)
Browse files Browse the repository at this point in the history
In this commit, we remove constructor parameters to ensure compatibility with the
`useDefineForClassFields` compiler option. When public class fields are enabled, such cases
throw a `TS2729: Property used before its initialization` error.
This occurs when a property (e.g., an injectee) is referenced before the constructor is executed.

Additionally, we are removing property decorators such as `@Inject` with this change.
Property decorators are not permitted when experimental decorators are disabled.
This adjustment helps make the framework compatible with these TypeScript compiler
options moving forward.
  • Loading branch information
arturovt authored Nov 18, 2024
1 parent 4720c7c commit c73c22f
Show file tree
Hide file tree
Showing 16 changed files with 86 additions and 107 deletions.
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ $ npm install @ngxs/store@dev

### To become next patch version

- Refactor: Use field initializers for injectees [#2258](https://github.com/ngxs/store/pull/2258)
- Fix(websocket-plugin): Do not dispatch action when root injector is destroyed [#2257](https://github.com/ngxs/store/pull/2257)

### 18.1.5 2024-11-12
Expand Down
19 changes: 7 additions & 12 deletions packages/devtools-plugin/src/devtools.plugin.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { Inject, Injectable, Injector, NgZone, OnDestroy, ɵglobal } from '@angular/core';
import { inject, Injectable, Injector, NgZone, OnDestroy, ɵglobal } from '@angular/core';
import { Store } from '@ngxs/store';
import {
InitState,
Expand All @@ -8,12 +8,7 @@ import {
} from '@ngxs/store/plugins';
import { tap, catchError } from 'rxjs/operators';

import {
NGXS_DEVTOOLS_OPTIONS,
NgxsDevtoolsAction,
NgxsDevtoolsExtension,
NgxsDevtoolsOptions
} from './symbols';
import { NGXS_DEVTOOLS_OPTIONS, NgxsDevtoolsAction, NgxsDevtoolsExtension } from './symbols';

const enum ReduxDevtoolsActionType {
Dispatch = 'DISPATCH',
Expand All @@ -33,17 +28,17 @@ const enum ReduxDevtoolsPayloadType {
*/
@Injectable()
export class NgxsReduxDevtoolsPlugin implements OnDestroy, NgxsPlugin {
private _injector = inject(Injector);
private _ngZone = inject(NgZone);
private _options = inject(NGXS_DEVTOOLS_OPTIONS);

private devtoolsExtension: NgxsDevtoolsExtension | null = null;
private readonly globalDevtools =
ɵglobal['__REDUX_DEVTOOLS_EXTENSION__'] || ɵglobal['devToolsExtension'];

private unsubscribe: VoidFunction | null = null;

constructor(
@Inject(NGXS_DEVTOOLS_OPTIONS) private _options: NgxsDevtoolsOptions,
private _injector: Injector,
private _ngZone: NgZone
) {
constructor() {
this.connect();
}

Expand Down
4 changes: 3 additions & 1 deletion packages/devtools-plugin/src/symbols.ts
Original file line number Diff line number Diff line change
Expand Up @@ -81,4 +81,6 @@ export interface NgxsDevtoolsOptions {
traceLimit?: number;
}

export const NGXS_DEVTOOLS_OPTIONS = new InjectionToken('NGXS_DEVTOOLS_OPTIONS');
export const NGXS_DEVTOOLS_OPTIONS = new InjectionToken<NgxsDevtoolsOptions>(
'NGXS_DEVTOOLS_OPTIONS'
);
14 changes: 6 additions & 8 deletions packages/form-plugin/src/directive.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { ChangeDetectorRef, Directive, Input, OnDestroy, OnInit } from '@angular/core';
import { ChangeDetectorRef, Directive, inject, Input, OnDestroy, OnInit } from '@angular/core';
import { FormGroup, FormGroupDirective } from '@angular/forms';
import { Actions, ofActionDispatched, Store } from '@ngxs/store';
import { getValue } from '@ngxs/store/plugins';
Expand Down Expand Up @@ -38,14 +38,12 @@ export class NgxsFormDirective implements OnInit, OnDestroy {

private _updating = false;

private readonly _destroy$ = new ReplaySubject<void>(1);
private _actions$ = inject(Actions);
private _store = inject(Store);
private _formGroupDirective = inject(FormGroupDirective);
private _cd = inject(ChangeDetectorRef);

constructor(
private _actions$: Actions,
private _store: Store,
private _formGroupDirective: FormGroupDirective,
private _cd: ChangeDetectorRef
) {}
private readonly _destroy$ = new ReplaySubject<void>(1);

ngOnInit() {
this._actions$
Expand Down
22 changes: 9 additions & 13 deletions packages/router-plugin/src/router.state.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { NgZone, Injectable, OnDestroy, Injector } from '@angular/core';
import { NgZone, Injectable, OnDestroy, inject } from '@angular/core';
import {
NavigationCancel,
NavigationError,
Expand All @@ -13,7 +13,6 @@ import {
import { Action, createSelector, State, StateContext, StateToken, Store } from '@ngxs/store';
import {
NavigationActionTiming,
NgxsRouterPluginOptions,
ɵNGXS_ROUTER_PLUGIN_OPTIONS
} from '@ngxs/router-plugin/internals';
import { ReplaySubject } from 'rxjs';
Expand Down Expand Up @@ -59,6 +58,12 @@ export const ROUTER_STATE_TOKEN = new StateToken<RouterStateModel>('router');
})
@Injectable()
export class RouterState implements OnDestroy {
private _store = inject(Store);
private _router = inject(Router);
private _serializer: RouterStateSerializer<RouterStateSnapshot> =
inject(RouterStateSerializer);
private _ngZone = inject(NgZone);

/**
* Determines how navigation was performed by the `RouterState` itself
* or outside via `new Navigate(...)`
Expand All @@ -77,7 +82,7 @@ export class RouterState implements OnDestroy {

private _lastEvent: Event | null = null;

private _options: NgxsRouterPluginOptions | null = null;
private _options = inject(ɵNGXS_ROUTER_PLUGIN_OPTIONS);

private _destroy$ = new ReplaySubject<void>(1);

Expand All @@ -95,16 +100,7 @@ export class RouterState implements OnDestroy {
state => state?.state?.url
);

constructor(
private _store: Store,
private _router: Router,
private _serializer: RouterStateSerializer<RouterStateSnapshot>,
private _ngZone: NgZone,
injector: Injector
) {
// Note: do not use `@Inject` since it fails on lower versions of Angular with Jest
// integration, it cannot resolve the token provider.
this._options = injector.get(ɵNGXS_ROUTER_PLUGIN_OPTIONS, null);
constructor() {
this._setUpStoreListener();
this._setUpRouterEventsListener();
}
Expand Down
14 changes: 5 additions & 9 deletions packages/storage-plugin/src/storage.plugin.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { PLATFORM_ID, Inject, Injectable, inject } from '@angular/core';
import { PLATFORM_ID, Injectable, inject } from '@angular/core';
import { isPlatformServer } from '@angular/common';
import { ɵPlainObject } from '@ngxs/store/internals';
import {
Expand All @@ -13,7 +13,6 @@ import {
import {
ɵDEFAULT_STATE_KEY,
ɵALL_STATES_PERSISTED,
NgxsStoragePluginOptions,
ɵNGXS_STORAGE_PLUGIN_OPTIONS
} from '@ngxs/storage-plugin/internals';
import { tap } from 'rxjs/operators';
Expand All @@ -27,16 +26,13 @@ const NG_DEV_MODE = typeof ngDevMode !== 'undefined' && ngDevMode;

@Injectable()
export class NgxsStoragePlugin implements NgxsPlugin {
private _keysManager = inject(ɵNgxsStoragePluginKeysManager);
private _options = inject(ɵNGXS_STORAGE_PLUGIN_OPTIONS);
private _allStatesPersisted = inject(ɵALL_STATES_PERSISTED);

constructor(
private _keysManager: ɵNgxsStoragePluginKeysManager,
@Inject(ɵNGXS_STORAGE_PLUGIN_OPTIONS) private _options: NgxsStoragePluginOptions,
@Inject(PLATFORM_ID) private _platformId: string
) {}
private _isServer = isPlatformServer(inject(PLATFORM_ID));

handle(state: any, event: any, next: NgxsNextPluginFn) {
if (isPlatformServer(this._platformId)) {
if (this._isServer) {
return next(state, event);
}

Expand Down
10 changes: 5 additions & 5 deletions packages/store/src/actions-stream.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { Injectable, OnDestroy } from '@angular/core';
import { inject, Injectable, OnDestroy } from '@angular/core';
import { ɵOrderedSubject } from '@ngxs/store/internals';
import { Observable, Subject, filter, share } from 'rxjs';

Expand Down Expand Up @@ -50,10 +50,10 @@ export class InternalActions extends ɵOrderedSubject<ActionContext> implements
*/
@Injectable({ providedIn: 'root' })
export class Actions extends Observable<ActionContext> {
constructor(
internalActions$: InternalActions,
internalExecutionStrategy: InternalNgxsExecutionStrategy
) {
constructor() {
const internalActions$ = inject(InternalActions);
const internalExecutionStrategy = inject(InternalNgxsExecutionStrategy);

const sharedInternalActions$ = internalActions$.pipe(
leaveNgxs(internalExecutionStrategy),
// The `InternalActions` subject emits outside of the Angular zone.
Expand Down
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
import { Inject, Injectable } from '@angular/core';
import { inject, Injectable } from '@angular/core';
import { InitState, UpdateState, getActionTypeFromInstance } from '@ngxs/store/plugins';

import { ActionType } from '../actions/symbols';
import { NgxsDevelopmentOptions, NGXS_DEVELOPMENT_OPTIONS } from './symbols';
import { NGXS_DEVELOPMENT_OPTIONS } from './symbols';

@Injectable()
export class NgxsUnhandledActionsLogger {
Expand All @@ -12,7 +12,8 @@ export class NgxsUnhandledActionsLogger {
*/
private _ignoredActions = new Set<string>([InitState.type, UpdateState.type]);

constructor(@Inject(NGXS_DEVELOPMENT_OPTIONS) options: NgxsDevelopmentOptions) {
constructor() {
const options = inject(NGXS_DEVELOPMENT_OPTIONS);
if (typeof options.warnOnUnhandledActions === 'object') {
this.ignoreActions(...options.warnOnUnhandledActions.ignore);
}
Expand Down
Original file line number Diff line number Diff line change
@@ -1,22 +1,22 @@
import { Inject, Injectable, NgZone, PLATFORM_ID } from '@angular/core';
import { inject, Injectable, NgZone, PLATFORM_ID } from '@angular/core';
import { isPlatformServer } from '@angular/common';

import { NgxsExecutionStrategy } from './symbols';
import { getZoneWarningMessage } from '../configs/messages.config';

@Injectable({ providedIn: 'root' })
export class DispatchOutsideZoneNgxsExecutionStrategy implements NgxsExecutionStrategy {
constructor(
private _ngZone: NgZone,
@Inject(PLATFORM_ID) private _platformId: string
) {
private _ngZone = inject(NgZone);
private _isServer = isPlatformServer(inject(PLATFORM_ID));

constructor() {
if (typeof ngDevMode !== 'undefined' && ngDevMode) {
verifyZoneIsNotNooped(_ngZone);
verifyZoneIsNotNooped(this._ngZone);
}
}

enter<T>(func: () => T): T {
if (isPlatformServer(this._platformId)) {
if (this._isServer) {
return this.runInsideAngular(func);
}
return this.runOutsideAngular(func);
Expand Down
Original file line number Diff line number Diff line change
@@ -1,12 +1,10 @@
import { Injectable, Inject } from '@angular/core';
import { Injectable, inject } from '@angular/core';

import { NgxsExecutionStrategy, NGXS_EXECUTION_STRATEGY } from './symbols';

@Injectable({ providedIn: 'root' })
export class InternalNgxsExecutionStrategy implements NgxsExecutionStrategy {
constructor(
@Inject(NGXS_EXECUTION_STRATEGY) private _executionStrategy: NgxsExecutionStrategy
) {}
private _executionStrategy = inject(NGXS_EXECUTION_STRATEGY);

enter<T>(func: () => T): T {
return this._executionStrategy.enter(func);
Expand Down
16 changes: 7 additions & 9 deletions packages/store/src/internal/dispatcher.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { Injectable, NgZone } from '@angular/core';
import { inject, Injectable, NgZone } from '@angular/core';
import { EMPTY, forkJoin, Observable, of, Subject, throwError } from 'rxjs';
import { exhaustMap, filter, map, shareReplay, take } from 'rxjs/operators';

Expand All @@ -23,14 +23,12 @@ export class InternalDispatchedActionResults extends Subject<ActionContext> {}

@Injectable({ providedIn: 'root' })
export class InternalDispatcher {
constructor(
private _ngZone: NgZone,
private _actions: InternalActions,
private _actionResults: InternalDispatchedActionResults,
private _pluginManager: PluginManager,
private _stateStream: ɵStateStream,
private _ngxsExecutionStrategy: InternalNgxsExecutionStrategy
) {}
private _ngZone = inject(NgZone);
private _actions = inject(InternalActions);
private _actionResults = inject(InternalDispatchedActionResults);
private _pluginManager = inject(PluginManager);
private _stateStream = inject(ɵStateStream);
private _ngxsExecutionStrategy = inject(InternalNgxsExecutionStrategy);

/**
* Dispatches event(s).
Expand Down
14 changes: 6 additions & 8 deletions packages/store/src/internal/lifecycle-state-manager.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { Injectable, OnDestroy } from '@angular/core';
import { inject, Injectable, OnDestroy } from '@angular/core';
import { ɵNgxsAppBootstrappedState } from '@ngxs/store/internals';
import { getValue, InitState, UpdateState } from '@ngxs/store/plugins';
import { ReplaySubject } from 'rxjs';
Expand All @@ -15,17 +15,15 @@ const NG_DEV_MODE = typeof ngDevMode !== 'undefined' && ngDevMode;

@Injectable({ providedIn: 'root' })
export class LifecycleStateManager implements OnDestroy {
private _store = inject(Store);
private _internalStateOperations = inject(InternalStateOperations);
private _stateContextFactory = inject(StateContextFactory);
private _appBootstrappedState = inject(ɵNgxsAppBootstrappedState);

private readonly _destroy$ = new ReplaySubject<void>(1);

private _initStateHasBeenDispatched?: boolean;

constructor(
private _store: Store,
private _internalStateOperations: InternalStateOperations,
private _stateContextFactory: StateContextFactory,
private _appBootstrappedState: ɵNgxsAppBootstrappedState
) {}

ngOnDestroy(): void {
this._destroy$.next();
}
Expand Down
4 changes: 2 additions & 2 deletions packages/store/src/internal/state-context-factory.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { Injectable } from '@angular/core';
import { inject, Injectable } from '@angular/core';
import { getValue, setValue } from '@ngxs/store/plugins';
import { ExistingState, StateOperator, isStateOperator } from '@ngxs/store/operators';
import { Observable } from 'rxjs';
Expand All @@ -14,7 +14,7 @@ import { simplePatch } from './state-operators';
*/
@Injectable({ providedIn: 'root' })
export class StateContextFactory {
constructor(private _internalStateOperations: InternalStateOperations) {}
private _internalStateOperations = inject(InternalStateOperations);

/**
* Create the state context
Expand Down
10 changes: 4 additions & 6 deletions packages/store/src/internal/state-operations.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { Injectable } from '@angular/core';
import { inject, Injectable } from '@angular/core';
import { ɵStateStream } from '@ngxs/store/internals';

import { StateOperations, StatesAndDefaults } from '../internal/internals';
Expand All @@ -11,11 +11,9 @@ import { deepFreeze } from '../utils/freeze';
*/
@Injectable({ providedIn: 'root' })
export class InternalStateOperations {
constructor(
private _stateStream: ɵStateStream,
private _dispatcher: InternalDispatcher,
private _config: NgxsConfig
) {}
private _stateStream = inject(ɵStateStream);
private _dispatcher = inject(InternalDispatcher);
private _config = inject(NgxsConfig);

/**
* Returns the root state operators.
Expand Down
Loading

0 comments on commit c73c22f

Please sign in to comment.