Mark
Mark

Reputation: 2587

Angular NgRX: going to the DB if the store slice is empty

I'm using Angular NgRX. I am stilling learning NgRX. In the code below. I'm using a selector to get the state slice from the store. If the data is undefined or the length is equal to zero then call dispatch which will then goto the DB to get the data.

My question is: Is this the right way to do this? Or should NgRX Effects handle the logic of going to the API if the store slice is undefined?

In the code below, if I don't subscribe to the selector and check it then it will call the API end-point every time, which defeats the purpose of using a state store, yeah?

import { Component, OnInit, OnDestroy } from '@angular/core';
import { select, Store } from '@ngrx/store';
import { Observable, Subscription } from 'rxjs';
import { tap } from 'rxjs/operators';
...

@Component({
  selector: 'my-pro',
  templateUrl: './my-pro.component.html',
  styleUrls: ['./my-pro.component.scss'],
})

export class MyProxyComponent implements OnInit, OnDestroy {

  myProxyList$ = this.store.select(getProxyList);
  subs: Subscription;
  searchCriteria = {
    standardId: 'mka',
    start: 1,
    end: 25,
  };

  constructor(
    private store: Store<State>,
  ) {
  }

  ngOnInit(): void {
    this.getMyProxy();
  }
 
  ngOnDestroy(): void {
    this.subs.unsubscribe();
  }

  getMyProxy(): void {
    this.subs = this.myProxyList$.subscribe(
      data => {
        console.log('myProxyList', data);
        // if length is 0, goto the db
        if (data === undefined || data.length === 0) {
           // ngrx action to call api
           this.store.dispatch(ProxyActions.getAllProxyList({ searchCriteria: this.searchCriteria }));
         }
       }
     );
  }

}

Upvotes: 1

Views: 627

Answers (2)

lazurey
lazurey

Reputation: 327

David Rinck's answer works perfect for me. Just add a full code example based on it:

fetchItems$ = createEffect((
      action$ = inject(Actions),
      service = inject(YourService),
      store = inject(Store)
    ) => action$.pipe(
      ofType(YourActions.fetchItems),
      withLatestFrom(store.select(YourFeature.selectItems)),
      filter(([, state]) => {
        // change to your own empty predication
        return state.data.length === 0;
      }),
      exhaustMap(() => service.fetchAll().pipe(
        map((data) => YourActions.fetchItemsSuccess({ data })),
        catchError(({ error }: HttpErrorResponse) => of(
          YourActions.fetchItemsFailure({
            error
          })
        )),
      )),
    ), { functional: true }
  )

Upvotes: 0

David Rinck
David Rinck

Reputation: 6996

You definitely want to do this in your Effects. It is a lot of boiler plate. If it seems like too much, look in to @ngrx/component-store or the rxjs facade. They have less indirection, and in your use case, especially for just one component, they may be a better fit. See for example, the search-tickets example here: https://medium.com/angular-in-depth/angular-you-may-not-need-ngrx-e80546cc56ee

For the ngrx solution, add a proxyListLoaded boolean in your Store to track if it's already been loaded.


 proxyList$ = createEffect(() =>
    this.actions$.pipe(
      ofType(proxyActions.getAllProxyList),
      withLatestFrom(
        this.store.select(getProxyListLoaded),
      ),
      filter(([_action, loaded]) => !loaded),
      exhaustMap(() => 
        this.proxyService.getProxyList().pipe(
        map(result => new LoadOrders(result))
      ))
  );

Upvotes: 2

Related Questions