1

In my application I retrieve all data in the "Items-Overview-Component" in the constructor. (router path: "/items").

// Items-Overview-Component

constructor(private store: Store<fromReducer.State>) {
    this.store.dispatch(loadItems());
}

When clicking on an item entry, I navigate to "/items/:id". Then I retrieve the details from the store using the :id.

// Item-Details-Component

constructor(private store: Store<fromReducer.State>) {
    this.item$ = this.store.pipe(select(getItemDetails));
}

So far a very clear vanilla use case for an Angular project.

However, if the user navigates "hard" to a URL with a specific item (e.g. "/items/741"), then the store is empty and no details can be retrieved.

How can I reload the data in NGRX "on demand"? Is there perhaps a one-time lifecycle hook that I should react to here?

BTW: I also use @ngrx/router-store

EchtFettigerKeks
  • 1,692
  • 1
  • 18
  • 33

1 Answers1

2

The pattern you want is most likely to use a guard on the /items route, instead of relying on the Items-Overview-Component to trigger loading items.

It could look like:

@Injectable({
  providedIn: 'root',
})
export class ItemsGuard implements CanActivate {
  constructor(
    private store: Store<AppState>
  ) {}

  public canActivate(): Observable<boolean> {
    this.store.dispatch(loadItems()); // immediately trigger loading items

    return this.store.select(getItems).pipe(
      filter(items => !!items && items.length) // wait until the selector emits some non-empty items list
      mapTo(true) // as soon as the items are present, allow the navigation event to complete
    );
  }
}

Then you can use it in your routes file like this:

    path: 'items',
    canActivate: [ItemsGuard],
    ....
    children: [ 
      // '/:id' route goes here
    ]

Since items/:id is configured as a child route of /items, ItemsGuard will trigger in both cases, even if after page refresh you directly enter /items/123 without ever entering /items.

JoannaFalkowska
  • 2,771
  • 20
  • 37
  • Hey thanks for this pattern! That helped me. Apart from that, I wonder how I could conditionally execute this "dispatch" (which in turn triggers an effect with very slow API requests) within this guard, i.e. only if the data has not yet been loaded. Can you give me another tip here? – EchtFettigerKeks Jul 12 '22 at 15:36
  • @EchtFettigerKeks In short, you would need to subscribe to the Observable that emits your data, and then dispatch or not dispatch based on the emitted value. Just make sure to add `take(1)` to it, so that the condition check and the resulting dispatch happen only once, even if somehow more values get emitted later. So full oneliner would be along the lines of: `store.select(yourSelector).pipe(take(1)).subscribe(value => { if (value) { store.dispatch.... }})` – JoannaFalkowska Jul 13 '22 at 18:10