2

I've got an Angular route resolver that has a list of items that gets loaded when my parent route gets loaded, like /users. I have implemented an Angular resolver that looks more or less like this:

export class UsersResolver implements Resolve<UsersResponse> {
  constructor(private usersService: UsersService) {}

  resolve(): Observable<UsersResponse> {
    return this.usersService.getUsers();
  }
}

The child components consume that resolver data in the standard way:

this.route.data.subscribe((data) => {const users = data['users']} )

That works fine...until I perform any action that might change that list (add/delete/alter a user). In that case, I want that resolver to re-run. Well, more specifically, I want the data returned by the resolver to reflect the updated data. I don't think the static options on runGuardsAndResolvers will do the trick, nor do I think in my case that using the function you can hand to runGuardsAndResolvers will work in my case, either, because these data changes might not happen in concert with a route change.

I think what I want is something along the lines of this:

export class UsersResolver implements Resolve<UsersResponse> {
  constructor(private usersService: UsersService) {}

  resolve(): Observable<UsersResponse> {
    return this.usersService.users$;
  }
}

...where this.usersService.users$ is an rxjs Subject. That would mean I could update that subject via other means and (I think) the data in the resolver would update, and therefore any child component subscribed to that resolver would get updated. But if that's right, then where do I trigger the initial data fetch, and how do I ensure that my resolver doesn't actually resolve until the initial data fetch completes?

Is this a common issue with a reasonable solution, or am I going about this in the wrong way?

hairbo
  • 3,113
  • 2
  • 27
  • 34

3 Answers3

1

I think you are trying something that is not possible. I mean the resolver will be triggered in the route change event so if you change something for that observable after the resolver was completed you won't get that information in your component.

If you want to sync that information with your component I recommend you to subscribe or just use async pipe in your component instead of doing it in your resolver.

If you still want to have your resolver you can merge both observables and handle the changes in your component

  • Thanks for the reply. So my `users` data doesn't change that much, and is needed by a bunch of child components along my `users` route. Are you saying that rather than using the resolver approach, I'd need to fetch/subscribe to my users data in every child component? `async pipe` doesn't help me, because I need that data in the TS component file, not just in templates for display. There's got to be a way of doing what I want, right? It seems like a very common pattern: get data once that is shared, but don't load children until that data resolves. – hairbo Nov 01 '22 at 18:14
  • You should't fetch in every child component. You can create a service to manage the state for it. You can use plain observables for it or using another dependency like ngrx. Then you can update that state every time that you create/update/delete a user. Another way is to manage the state in your parent component but it will be harder to maintain in the future. You can follow this example how to have a service to comunicate component https://github.com/heberlopezz/obervables – Jonathan Lopez Nov 01 '22 at 19:35
  • Right. Sorry, I was being a little glib. if I use a service, then every child has to check to make sure that the `users` data is there. If I use a resolver at the route level, then child components know that `users` data is going to be there. From a coding perspective, that's better (in some cases, a lot better). Also, conceptually, getting the data at the route level is much cleaner. So the fact that I can't then update that data and have it propagate back down via the resolver and `this.route.data` is frustrating. – hairbo Nov 01 '22 at 19:40
  • You can do it with both approach I mean, if you subscribe to a service where you have the state of that part of the application in your parent it will be the same that you did with the resolver – Jonathan Lopez Nov 02 '22 at 22:44
1

Angular resolver is there just to provide data while navigation and should not do more.

There are many ways to keep data consistent in your application.

One known way is the state management (store), there are many libraries out there like NgRx that offer the necessary tools to keep your data consistent.

Read this article about state management for more details.

Another way is to build your own simple store concept, by creating a singleton service that spread the change of your data. Then you can subscribe and handle the change in your components.

in your user data service:

data$: Observable<any>;

in your component

data: any;
data$.subscribe(data => this.data = data);

somewhere else in other component

data$.next(data);

Other way is to store your data in a singleton store service and access it directly from your components. The service will have the update and get methods to set and update the data in the service.

Mehyar Sawas
  • 1,187
  • 6
  • 15
  • Thanks for this. I'm familiar with state management systems from my time using React. I like the route resolver approach, because it looks like a way to ensure that a route and its children all have the necessary data before rendering anything. However, it sounds like the route resolver data should be treated as a snapshot of data, as opposed to data that may be updated over time. If that's really the case, then I probably can't use route resolvers for my this particular need, which makes me sad. – hairbo Nov 01 '22 at 20:12
  • Sorry to head that! You still can use the route resolver, but then you should take care of updating the data after navigation. – Mehyar Sawas Nov 01 '22 at 20:19
  • Is my solution helpful for you, or should I remove it? – Mehyar Sawas Nov 01 '22 at 20:29
  • Leave it here, please. I think it's the right way to go in my case. I've got a Subject that I populate with data in my parent component, then children subscribe to that Subject rather than `this.route.data`. Sigh. Less elegant for sure, or at least less clear, but more flexible. I'll mark yours as the right answer, even though both answers are helpful. – hairbo Nov 01 '22 at 20:50
  • You could also keep your resolver and besides use the rxjs subject to update your data. However I also see the resolver as an elegant way to preload the data before navigating, but still does not replace the state management. – Mehyar Sawas Nov 01 '22 at 21:06
0

It's possible, a little hacky. You might just want to inject UsersService in the component.

But down the garden path:

We return an observable of an observable with a little trick to ensure first emission has occurred and we don't lose this first value.

  resolve(): Observable<Observable<number[]>> {
    const users$ = this.usersService.getUsers();
    return users$.pipe(
      take(1),
      map((firstValue) => {
        return users$.pipe(startWith(firstValue))
      })
    );
  }

Stackblitz

Andrew Allen
  • 6,512
  • 5
  • 30
  • 73
  • `take(1)` isn't needed since resolver take one value but I tend to put it in to be self documenting. – Andrew Allen Nov 01 '22 at 21:42
  • So if you go this route, if and when `this.usersService.getUsers()` produces a new value, this resolver will "re-run" and downstream components will pick up those new values? It does seem a little hacky, but thanks for posting – hairbo Nov 02 '22 at 01:13
  • Re-run is probably not the correct term. The observable `users$.pipe(startWith(firstValue)` which acts like `users$` is being handed off to the routed component as a static variable – Andrew Allen Nov 02 '22 at 07:05
  • I have much to learn about RxJS still. (-; I did just notice that `startsWith` has been deprecated. I'm not sure how dramatically that affects your solution. – hairbo Nov 02 '22 at 14:30
  • It's not deprecated. Only certain function signatures. See https://stackoverflow.com/q/56571406/4711754 – Andrew Allen Nov 02 '22 at 14:34
  • Ah. Well that's not confusing or anything. (-; – hairbo Nov 02 '22 at 16:19