platform
                                
                                 platform copied to clipboard
                                
                                    platform copied to clipboard
                            
                            
                            
                        ComponentStore effect without parameters
Information
A question that I frequently see on various channels, is how to implement an effect without parameters. Because this seems to be the number one question, I propose to add an example to the docs.
Documentation page
https://ngrx.io/guide/component-store/effect
I would be willing to submit a PR to fix this issue
- [X] Yes
- [ ] No
Building on the existing documentation example here. Might not be 100% accurate as I'm writing the code from memory.
Using standalone pipe
import { ComponentStore, tapResponse } from '@ngrx/component-store';
import { pipe, switchMap } from 'rxjs';
import { Movie } from './movie';
import { MoviesService } from './movies.service';
interface MoviesState {
  readonly movies: readonly Movie[];
}
@Injectable()
export class MoviesStore extends ComponentStore<MoviesState> {
  movies$: Observable<readonly Movie[]> = this.select(state => state.movies);
  constructor() {
    super(initialState);
    // Preload all movies
    this.#getAllMovies();
  }
  #getAllMovies = this.effect<void>(
    // Standalone observable chain. An Observable<void> will be attached by ComponentStore.
    pipe(
      switchMap(() => this.moviesService.fetchAllMovies().pipe(
        tapResponse(
          movies => this.#replaceMovies(movies),
          (error: HttpErrorResponse) => this.logError(error),
        )
      )
    )
  );
  
  #replaceMovies = this.updater<readonly Movie[]>((state, movies): MoviesState => ({
    ...state,
    movies,
  }));
}
const initialState: MoviesState = {
  movies: [],
};
Using trigger$/source$
import { ComponentStore, tapResponse } from '@ngrx/component-store';
import { pipe, switchMap } from 'rxjs';
import { Movie } from './movie';
import { MoviesService } from './movies.service';
interface MoviesState {
  readonly movies: readonly Movie[];
}
@Injectable()
export class MoviesStore extends ComponentStore<MoviesState> {
  movies$: Observable<readonly Movie[]> = this.select(state => state.movies);
  constructor() {
    super(initialState);
    // Preload all movies
    this.#getAllMovies();
  }
  #getAllMovies = this.effect<void>(
    // Can be named whatever, for example source$. The name doesn't matter.
    trigger$ => trigger$.pipe(
      switchMap(() => this.moviesService.fetchAllMovies().pipe(
        tapResponse(
          movies => this.#replaceMovies(movies),
          (error: HttpErrorResponse) => this.logError(error),
        )
      )
    )
  );
  
  #replaceMovies = this.updater<readonly Movie[]>((state, movies): MoviesState => ({
    ...state,
    movies,
  }));
}
const initialState: MoviesState = {
  movies: [],
};
Using of
import { ComponentStore, tapResponse } from '@ngrx/component-store';
import { pipe, switchMap } from 'rxjs';
import { Movie } from './movie';
import { MoviesService } from './movies.service';
interface MoviesState {
  readonly movies: readonly Movie[];
}
@Injectable()
export class MoviesStore extends ComponentStore<MoviesState> {
  movies$: Observable<readonly Movie[]> = this.select(state => state.movies);
  constructor() {
    super(initialState);
    // Preload all movies
    this.#getAllMovies();
  }
  #getAllMovies = this.effect<void>(
    // Synchronous observable emitting undefined once to kick off the effect
    () => of().pipe(
      switchMap(() => this.moviesService.fetchAllMovies().pipe(
        tapResponse(
          movies => this.#replaceMovies(movies),
          (error: HttpErrorResponse) => this.logError(error),
        )
      )
    )
  );
  
  #replaceMovies = this.updater<readonly Movie[]>((state, movies): MoviesState => ({
    ...state,
    movies,
  }));
}
const initialState: MoviesState = {
  movies: [],
};
Using state$
import { ComponentStore, tapResponse } from '@ngrx/component-store';
import { pipe, switchMap } from 'rxjs';
import { Movie } from './movie';
import { MoviesService } from './movies.service';
interface MoviesState {
  readonly movies: readonly Movie[];
}
@Injectable()
export class MoviesStore extends ComponentStore<MoviesState> {
  movies$: Observable<readonly Movie[]> = this.select(state => state.movies);
  constructor() {
    super(initialState);
    // Preload all movies
    this.#getAllMovies();
  }
  #getAllMovies = this.effect<void>(
    // Synchronous observable emitting undefined once to kick off the effect
    () => this.state$.pipe(
      take(1),
      switchMap(() => this.moviesService.fetchAllMovies().pipe(
        tapResponse(
          movies => this.#replaceMovies(movies),
          (error: HttpErrorResponse) => this.logError(error),
        )
      )
    )
  );
  
  #replaceMovies = this.updater<readonly Movie[]>((state, movies): MoviesState => ({
    ...state,
    movies,
  }));
}
const initialState: MoviesState = {
  movies: [],
};
Credits to @nartc for the standalone pipe technique.
Credits to @timdeschryver for the of technique.
Credits to @brandonroberts, @alex-okrushko for the state$ technique.
See RFC: Add "ngrxOnInitStore" lifecycle method to ComponentStore (#3335) for a library hook proposal that might support other techniques.
@timdeschryver I suggest you mention ComponentStore in the title of this issue to avoid confusion with NgRx Effects.
I use this for effects without params:
someEffect$ = this.effect(_ => _.pipe(
and this for effects with params:
someEffect$ = this.effect<SomeType>(_ => _.pipe(
Full examples
Without params:
  readonly createClass$ = this.effect(_ => _.pipe(
    withLatestFrom(this.select(s => s.model)),
    tap(([_, model]) => {
      // ... some code
    })
  ));
call:
  addClass() {
    this.store.createClass$();
  }
With param:
  readonly removeAttr$ = this.effect<string>(_ => _.pipe(
    withLatestFrom(this.editorStore.select(s => s.classForm)),
    exhaustMap(([attrKey, classForm]) => {
      //... some code
    })
  ));
call:
  removeAttr(attrKey: string) {
    this.store.removeAttr$(attrKey);
  }
Thanks for summing up all the different ways @LayZeeDK and @e-oz . Instead of mentioning all of them, I think that we should try to standardize one of these. Does one of these have a clear benefit over the others, or is this more a matter of preference?
As a side note, I don't think the new hook will create a new technique, but it introduces a way to do something (in most of the cases invoke an effect with or without params) when the store and state are initialized.
Instead of mentioning all of them, I think that we should try to standardize one of these.
Definitely. Besides gaining knowledge of available techniques, this is the reason for my initial questions regarding initializer effects on Discord earlier this year. Promoting a preferred technique is surely beneficial as has been the case for other NgRx packages.
Does one of these have a clear benefit over the others, or is this more a matter of preference?
Personally, I see the biggest potential in the standalone pipe technique since it is extractable to a separate constant or ES module (file) which allows us to test the observable chain in isolation.
The standalone pipe technique supports the point-free progamming style, removing the need for an intermediary variable with an arbitrary name (trigger$/source$/whatever$).
Besides parameterless effects, The standalone pipe technique is also a good fit for parameterized effects.
Playing the devil's advocate, the standalone pipe technique is a functional programming-style (as is a lot of RxJS) which is unfamiliar to some developers. This could be mitigated providing a more detailed explanation in addition to simple samples. Let's also not assume the worst without getting real world feedback.
I have a related question: Is the generic type for effect always required?
Effects without parameters require then the generic type void?
I made a small example here: https://stackblitz.com/edit/angular-ivy-jsnxmn?file=src%2Fapp%2Fstate.service.ts
The typings of effect (https://github.com/ngrx/platform/blob/13.1.0/modules/component-store/src/component-store.ts#L279) give the impression that void should be the default... I do not understand why we have to write explicitly effect<void>.
Also the docs do not use a generic type for effect: https://ngrx.io/guide/component-store/effect
Although it seems to be required.
@LayZeeDK
I think the trigger$/source$ is the most natural way. Also, that approach is used in the docs already.
standalone pipe: To me the standalone pipe always looks strange. I am so used to obs$.pipe in Angular :) Also the standalone pipe is not even needed if there is only one operator inside the pipe.
The of and state$ approach feel like a hack/work-around. Why create a new Observable or use a totally unrelated one (state$) if effect created it already (trigger$ / source$)?
I think the trigger$/source$ is the most natural way. Also, that approach is used in the docs already.
The standalone pipe technique additionally works well for parameterized effects. I agree that consistency is worth considering but maybe the current suggestion is not the best.
Also the standalone pipe is not even needed if there is only one operator inside the pipe.
That's an interesting point. But how is it different than having trigger$.pipe(singleOperation())? trigger$ isn't needed at all for most effects yet .pipe is also needed even for a single operation when using trigger$.
I still see isolated testability (and potential reusability) as in favor of the standalone pipe technique.
I have a related question: Is the generic type for
effectalways required? Effects without parameters require then the generic typevoid?
If we leave out the generic, the default parameter type is unknown.
The typings of effect (https://github.com/ngrx/platform/blob/13.1.0/modules/component-store/src/component-store.ts#L279) give the impression that void should be the default... I do not understand why we have to write explicitly effect
. Also the docs do not use a generic type for effect: https://ngrx.io/guide/component-store/effect 
If you use trigger$, then the type is inferred from the type of trigger$
this.effect((movieId$: Observable<string>) => movieId$.pipe());
Not sure why NgRx folks go with the route of typing with Observable rather than using the Generic. Using generic does not require you to import Observable symbol as well as faster/shorter to type.
With the standalone-pipe, there is nothing to infer so the Generic is required.
That's an interesting point. But how is it different than having
trigger$.pipe(singleOperation())?trigger$isn't needed at all for most effects yet.pipeis also needed even for a single operation when usingtrigger$.
@LayZeeDK you could do something like this with a single operation (e.g. mergeMap):
this.effect(mergeMap(v => doSomeStuff))
which is basically the same as:
this.effect(trigger$ => trigger$.pipe(mergeMap(v => doSomeStuff)))
which is basically the same as:
this.effect(pipe(mergeMap(v => doSomeStuff)))
In the last example the standalone pipe is useless, cause there is only one operator passed to the pipe.
you could do something like this with a single operation (e.g. mergeMap):
To me, this.effect(mergeMap(v => doSomeStuff)) is closer to this.effect(pipe(mergeMap(v => doSomeStuff))) than this.effect(trigger$ => trigger$.pipe(mergeMap(v => doSomeStuff))).
In the last example the standalone pipe is useless, cause there is only one operator passed to the pipe.
Except for leaving room for additional operations, both trigger$.pipe and pipe are useless for single operations.
I agree with @LayZeeDK.
The most common variant that I've used and seen is the snippet using the variable, e.g. trigger$ in combination with a pipe to execute an effect.
I think that if you're familiar with the syntax, you can use just pipe or xMap, but the friendliest one is the one using source$.pipe.
  getAllMovies = this.effect<void>(
    // Can be named whatever, for example source$. The name doesn't matter.
    trigger$ => trigger$.pipe(
      switchMap(() => this.moviesService.fetchAllMovies().pipe(
        tapResponse(
          movies => this.#replaceMovies(movies),
          (error: unknown) => this.logError(error),
        )
      )
    )
  );
I'm ok with using a similar snippet like the one that was used to illustrate an example.
Thoughts?
This thread deserves a BLOG POST!
@alvipeo I think I will write a blog post for this. I was thinking about it.
@timdeschryver I can take that one. Are we going with the trigger$ => trigger$ ?
Or we can maybe write two kind of solutions. I'm always using the standalone one which doesn't need to introduce a random name. But I get why people prefer the trigger$ one, it looks more like the rxjs pipe mental model.
@tomalaforge without trigger$ you can't call an effect without params - TS complaints.
@e-oz yes you can
  readonly updateTrackerContext = this.effect<number>(
    pipe(
      switchMap((trackerId) =>
        this.http.getTrackerContexts(trackerId).pipe(
          tapResponse(
            (contexts) => this.patchState({ contexts }),
            () => this.patchState({ contexts: undefined })
          )
        )
      )
    )
  );
now try to call store.updateTrackerContext(); and you’ll get TS error.
correction: it is possible to call it like this, if you add <void> as type information to effect definition.
Using trigger$ is good for me @tomalaforge
@timdeschryver can you assign me this Issue ?