I am migrating my app to Angular 18 and converting some parts to standalone. One of the first things I migrated was NGRX store.
I've had StoreModule, which imported FeatureModules. Two of them use SomePipe.
Now I have an index.ts which uses makeEnvironmentProviders
to gather all providers from Feature module.
const buildStore = () => makeEnvironmentProviders([
provideFeature1(),
provideFeature2(),
...
makeEnvironmentProviders([
provideStoreState({ name: 'feature-10', reducer }),
provideEffects(effects),
])
]);
As mentioned some effects (which are services) depend on a pipe (which is now standalone). When I had modules, I just provided/imported the pipe and it worked.
If I now put the pipe in one of my makeEnvironmentProviders
it will end up in app.config and thus be available everywhere. No scoping.
Is there a way to achieve such "scoping" using this approach?? How should it be done?
Using Pipe in an Effect might be discouraged, but for now it has to stay that way in our project.
To achieve proper scoping while using your Pipe logic in both the Pipe and Effect, I suggest abstracting the Pipe's logic into a separate function. This approach avoids the "hacky" way of globally providing the Pipe and makes your code cleaner and more maintainable.
inject
function.// pipe-logic.ts
import { inject } from '@angular/core';
import { SomeService } from './some.service';
export function injectPipeLogicFunc() {
const someService = inject(SomeService);
// Write your logic here
return (value: any, ...args: any[]) => {
const result = someService.someMethod(value, ...args);
return result;
};
}
// your-pipe.pipe.ts
import { Pipe, PipeTransform } from '@angular/core';
import { injectPipeLogicFunc } from './pipe-logic';
@Pipe({ name: 'yourPipe' })
export class YourPipe implements PipeTransform {
private pipeLogic = injectPipeLogicFunc();
transform(value: any, ...args: any[]): any {
return this.pipeLogic(value, ...args);
}
}
// some-effect.effect.ts
import { Injectable } from '@angular/core';
import { Actions, createEffect, ofType } from '@ngrx/effects';
import { map } from 'rxjs/operators';
import { someAction } from './actions';
import { injectPipeLogicFunc } from './pipe-logic';
@Injectable()
export class SomeEffect {
private pipeLogic = injectPipeLogicFunc();
constructor(private actions$: Actions) {}
someEffect = createEffect(() =>
this.actions$.pipe(
ofType(someAction),
map(({ payload }) => {
return this.pipeLogic(payload);
}),
)
);
}
Using this method, you can ensure that your logic stays organized and maintainable, fitting well within Angular's dependency injection system.