diff --git a/modules/effects/spec/effects_root_module.spec.ts b/modules/effects/spec/effects_root_module.spec.ts index 0b32d1bf5e..ec2cbc1f08 100644 --- a/modules/effects/spec/effects_root_module.spec.ts +++ b/modules/effects/spec/effects_root_module.spec.ts @@ -2,7 +2,7 @@ import { TestBed } from '@angular/core/testing'; import { INIT, Store, StoreModule } from '@ngrx/store'; import { EffectsModule } from '../src/effects_module'; -import { ROOT_EFFECTS_INIT } from '../src/effects_root_module'; +import { ROOT_EFFECTS_INIT } from '../src/effects_actions'; describe('Effects Root Module', () => { const foo = 'foo'; diff --git a/modules/effects/src/actions.ts b/modules/effects/src/actions.ts index 67df4209fa..29c0c01bed 100644 --- a/modules/effects/src/actions.ts +++ b/modules/effects/src/actions.ts @@ -8,7 +8,7 @@ import { import { Observable, OperatorFunction, Operator } from 'rxjs'; import { filter } from 'rxjs/operators'; -@Injectable() +@Injectable({ providedIn: 'root' }) export class Actions extends Observable { constructor(@Inject(ScannedActionsSubject) source?: Observable) { super(); diff --git a/modules/effects/src/effect_sources.ts b/modules/effects/src/effect_sources.ts index 77b3e2f55d..e9234f1b29 100644 --- a/modules/effects/src/effect_sources.ts +++ b/modules/effects/src/effect_sources.ts @@ -29,7 +29,7 @@ import { import { EFFECTS_ERROR_HANDLER } from './tokens'; import { getSourceForInstance, ObservableNotification } from './utils'; -@Injectable() +@Injectable({ providedIn: 'root' }) export class EffectSources extends Subject { constructor( private errorHandler: ErrorHandler, diff --git a/modules/effects/src/effects_actions.ts b/modules/effects/src/effects_actions.ts new file mode 100644 index 0000000000..a3febd4d64 --- /dev/null +++ b/modules/effects/src/effects_actions.ts @@ -0,0 +1,4 @@ +import { createAction } from '@ngrx/store'; + +export const ROOT_EFFECTS_INIT = '@ngrx/effects/init'; +export const rootEffectsInit = createAction(ROOT_EFFECTS_INIT); diff --git a/modules/effects/src/effects_module.ts b/modules/effects/src/effects_module.ts index 3298eb29e5..4c9fd1378c 100644 --- a/modules/effects/src/effects_module.ts +++ b/modules/effects/src/effects_module.ts @@ -7,17 +7,13 @@ import { SkipSelf, Type, } from '@angular/core'; -import { Actions } from './actions'; -import { EffectSources } from './effect_sources'; import { EffectsFeatureModule } from './effects_feature_module'; -import { defaultEffectsErrorHandler } from './effects_error_handler'; import { EffectsRootModule } from './effects_root_module'; import { EffectsRunner } from './effects_runner'; import { _FEATURE_EFFECTS, _ROOT_EFFECTS, _ROOT_EFFECTS_GUARD, - EFFECTS_ERROR_HANDLER, FEATURE_EFFECTS, ROOT_EFFECTS, USER_PROVIDED_EFFECTS, @@ -58,13 +54,6 @@ export class EffectsModule { return { ngModule: EffectsRootModule, providers: [ - { - provide: EFFECTS_ERROR_HANDLER, - useValue: defaultEffectsErrorHandler, - }, - EffectsRunner, - EffectSources, - Actions, rootEffects, { provide: _ROOT_EFFECTS, diff --git a/modules/effects/src/effects_root_module.ts b/modules/effects/src/effects_root_module.ts index 2146741dfa..1b548330f4 100644 --- a/modules/effects/src/effects_root_module.ts +++ b/modules/effects/src/effects_root_module.ts @@ -1,17 +1,9 @@ import { NgModule, Inject, Optional } from '@angular/core'; -import { - createAction, - StoreModule, - Store, - StoreRootModule, - StoreFeatureModule, -} from '@ngrx/store'; +import { Store, StoreRootModule, StoreFeatureModule } from '@ngrx/store'; import { EffectsRunner } from './effects_runner'; import { EffectSources } from './effect_sources'; import { ROOT_EFFECTS, _ROOT_EFFECTS_GUARD } from './tokens'; - -export const ROOT_EFFECTS_INIT = '@ngrx/effects/init'; -export const rootEffectsInit = createAction(ROOT_EFFECTS_INIT); +import { ROOT_EFFECTS_INIT } from './effects_actions'; @NgModule({}) export class EffectsRootModule { diff --git a/modules/effects/src/effects_runner.ts b/modules/effects/src/effects_runner.ts index 09f395ba23..ec4648473e 100644 --- a/modules/effects/src/effects_runner.ts +++ b/modules/effects/src/effects_runner.ts @@ -4,10 +4,14 @@ import { Subscription } from 'rxjs'; import { EffectSources } from './effect_sources'; -@Injectable() +@Injectable({ providedIn: 'root' }) export class EffectsRunner implements OnDestroy { private effectsSubscription: Subscription | null = null; + get isStarted(): boolean { + return !!this.effectsSubscription; + } + constructor( private effectSources: EffectSources, private store: Store diff --git a/modules/effects/src/index.ts b/modules/effects/src/index.ts index d57871d89a..5bc847ea89 100644 --- a/modules/effects/src/index.ts +++ b/modules/effects/src/index.ts @@ -11,14 +11,11 @@ export { EffectsMetadata, CreateEffectMetadata } from './models'; export { Actions, ofType } from './actions'; export { EffectsModule } from './effects_module'; export { EffectSources } from './effect_sources'; +export { ROOT_EFFECTS_INIT, rootEffectsInit } from './effects_actions'; export { EffectsRunner } from './effects_runner'; export { EffectNotification } from './effect_notification'; export { EffectsFeatureModule } from './effects_feature_module'; -export { - ROOT_EFFECTS_INIT, - rootEffectsInit, - EffectsRootModule, -} from './effects_root_module'; +export { EffectsRootModule } from './effects_root_module'; export { EFFECTS_ERROR_HANDLER } from './tokens'; export { act } from './act'; export { @@ -28,3 +25,4 @@ export { } from './lifecycle_hooks'; export { USER_PROVIDED_EFFECTS } from './tokens'; export { concatLatestFrom } from './concat_latest_from'; +export { provideEffects } from './provide_effects'; diff --git a/modules/effects/src/provide_effects.ts b/modules/effects/src/provide_effects.ts new file mode 100644 index 0000000000..d1fead9224 --- /dev/null +++ b/modules/effects/src/provide_effects.ts @@ -0,0 +1,64 @@ +import { ENVIRONMENT_INITIALIZER, inject, Provider, Type } from '@angular/core'; +import { Store } from '@ngrx/store'; +import { EffectsRunner } from './effects_runner'; +import { EffectSources } from './effect_sources'; +import { rootEffectsInit as effectsInit } from './effects_actions'; + +/** + * Runs provided effects. Can be called at the root or feature level. + * Unlike the `StoreModule.forRoot` method, this function does not need + * to be called at the root level if there are no root effects. + * + * @usageNotes + * + * ### Providing root effects + * + * ```ts + * bootstrapApplication(AppComponent, { + * providers: [provideEffects([RouterEffects])], + * }); + * ``` + * + * ### Providing feature effects + * + * ```ts + * const booksRoutes: Route[] = [ + * { + * path: '', + * providers: [provideEffects([BooksApiEffects])], + * children: [ + * { path: '', component: BookListComponent }, + * { path: ':id', component: BookDetailsComponent }, + * ], + * }, + * ]; + * ``` + */ +export function provideEffects(effects: Type[]): Provider[] { + return [ + effects, + { + provide: ENVIRONMENT_INITIALIZER, + multi: true, + useValue: () => { + const effectsRunner = inject(EffectsRunner); + const effectSources = inject(EffectSources); + const shouldInitEffects = !effectsRunner.isStarted; + + if (shouldInitEffects) { + effectsRunner.start(); + } + + for (const effectsClass of effects) { + const effectsInstance = inject(effectsClass); + effectSources.addEffects(effectsInstance); + } + + if (shouldInitEffects) { + const store = inject(Store); + store.dispatch(effectsInit()); + } + }, + }, + ]; +} diff --git a/modules/effects/src/tokens.ts b/modules/effects/src/tokens.ts index ca01eeb560..3a2600664f 100644 --- a/modules/effects/src/tokens.ts +++ b/modules/effects/src/tokens.ts @@ -1,5 +1,8 @@ import { InjectionToken, Type } from '@angular/core'; -import { EffectsErrorHandler } from './effects_error_handler'; +import { + defaultEffectsErrorHandler, + EffectsErrorHandler, +} from './effects_error_handler'; export const _ROOT_EFFECTS_GUARD = new InjectionToken( '@ngrx/effects Internal Root Guard' @@ -20,5 +23,6 @@ export const FEATURE_EFFECTS = new InjectionToken( '@ngrx/effects Feature Effects' ); export const EFFECTS_ERROR_HANDLER = new InjectionToken( - '@ngrx/effects Effects Error Handler' + '@ngrx/effects Effects Error Handler', + { providedIn: 'root', factory: () => defaultEffectsErrorHandler } ); diff --git a/modules/store/src/scanned_actions_subject.ts b/modules/store/src/scanned_actions_subject.ts index b34b44a20e..3200591f7f 100644 --- a/modules/store/src/scanned_actions_subject.ts +++ b/modules/store/src/scanned_actions_subject.ts @@ -3,7 +3,7 @@ import { Subject } from 'rxjs'; import { Action } from './models'; -@Injectable() +@Injectable({ providedIn: 'root' }) export class ScannedActionsSubject extends Subject implements OnDestroy