Search code examples
angularngrxngrx-storengrx-effects

How to send request to the server only once by using @NgRx/effects


Currently, I want to use NgRx on my project, and I have a question connected with getting data from the server.

For example, I have this code.

in component

// import ....

export class ToDoComponent implements OnInit {
  todo$: Observable<ToDoState>;
  ToDoSubscription: Subscription;
  ToDoList: ToDo[] = [];

  constructor(private store: Store<{ todos: ToDoState }>) {
    this.todo$ = store.pipe(select('todos'));
  }

  ngOnInit() {
    this.ToDoSubscription = this.todo$
      .pipe(
        map(x => {
          this.ToDoList = x.ToDos;
          this.todoError = x.ToDoError;
        })
      )
      .subscribe();

    this.store.dispatch(ToDoActions.BeginGetToDoAction());
  }
}

and this one

in Effects

// import ....

@Injectable()
export class ToDoEffects {
  constructor(private http: HttpClient, private action$: Actions) {}

  private ApiURL: string = 'https://localhost:44308/api/ToDo';

  GetToDos$: Observable<Action> = createEffect(() =>
    this.action$.pipe(
      ofType(ToDoActions.BeginGetToDoAction),
      mergeMap(action =>
        this.http.get(this.ApiURL).pipe(
          map((data: ToDo[]) => {
            return ToDoActions.SuccessGetToDoAction({ payload: data });
          }),
          catchError((error: Error) => {
            return of(ToDoActions.ErrorToDoAction(error));
          })
        )
      )
    )
  );
}

Does it mean that I will send a request to the server every time when the user navigates to the ToDoComponent component?

If yes, how can I configure my effect to send a request only once?

Or what is the best way for this case?

Any help will be appreciated.

Thanks!


Solution

  • Yes, everytime you navigate to the component it will fetch the todo.

    This is because of the dispatch line,

    this.store.dispatch(ToDoActions.BeginGetToDoAction());
    

    You can check if it's already present in the store as seen in Start using ngrx/effects for this

    @Effect()
    getOrder = this.actions.pipe(
      ofType<GetOrder>(ActionTypes.GetOrder),
      withLatestFrom(action =>
        of(action).pipe(
          this.store.pipe(select(getOrders))
        )
      ),
      filter(([{payload}, orders]) => !!orders[payload.orderId])
      mergeMap([{payload}] => {
        ...
      })
    )
    

    Or you can just put a take(1) in your effect:

      GetToDos$: Observable<Action> = createEffect(() =>
        this.action$.pipe(
          ofType(ToDoActions.BeginGetToDoAction),
          mergeMap(action =>
            this.http.get(this.ApiURL).pipe(
              map((data: ToDo[]) => {
                return ToDoActions.SuccessGetToDoAction({ payload: data });
              }),
              catchError((error: Error) => {
                return of(ToDoActions.ErrorToDoAction(error));
              })
            )
          ),
          take(1)
        )
      );
    

    here is the stackblitz example. https://stackblitz.com/edit/angular-d6jctm