Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Angular resolver not updating or refetching data, despite `runGuardsAndResolvers` set to 'always'?

I have a set of Angular routes, with a listing of entities, with two child routes for creation of such an entity and the editing of an existing entity. The listing of entities has a resolver attached to it to prefetch the data for the component before displaying it. These routes can be summarised as follows, see further down for how these routes are described in code.

  • index: /items
  • create: /items/create
  • edit: /items/:itemId/edit

However, if I am at /items/create, and successfully create an item, navigating "back" to /items or any edit route, or even back to / will not result in my resolver fetching updated data like I expect. This is despite having the runGuardsAndResolvers property set to "always". My understanding is that this property should enable to functionality I'm looking for.

Why is this, and how can I enable the functionality I'm looking for, without doing something like subscribing to router events in my component and duplicating logic.

Routes

const itemRoutes: Routes = [
    {
        path: '', // nb: this is a lazily loaded module that is itself a child of a parent, the _actual_ path for this is `/items`. 
        runGuardsAndResolvers: 'always',
        component: ItemsComponent,
        data: {
            breadcrumb: 'Items'
        },
        resolve: {
            items: ItemsResolver
        },
        children: [
            {
                path: 'create',
                component: CreateItemComponent,
                data: {
                    breadcrumb: 'Create Item'
                }
            },
            {
                path: ':itemId/edit',
                component: EditOrArchiveItemComponent,
                data: {
                    breadcrumb: 'Editing Item :item.name'
                },
                resolve: {
                    item: ItemResolver
                }
            }
        ]
    }
];

ItemsResolver

@Injectable()
export class ItemsResolver implements Resolve<ItemInIndex[]> {
    public constructor(public itemsHttpService: ItemsHttpService, private router: Router) {}

    public resolve(ars: ActivatedRouteSnapshot, rss: RouterStateSnapshot): Observable<ItemInIndex[]> {
        return this.itemsHttpService.index().take(1).map(items => {
            if (items) {
                return items;
            } else {
                this.router.navigate(['/']);
                return null;
            }
        });
    }
}

ItemsHttpService

(Posting at request)

@Injectable()
export class ItemsHttpService  {

    public constructor(private apollo: Apollo) {}

    public index(): Observable<ItemInIndex[]> {
        const itemsQuery = gql`
            query ItemsQuery {
                items {
                    id,
                    name
                }
            }
            `;

        return this.apollo.query<any>({
            query: itemsQuery
        }).pipe(
            map(result => result.data.items)
        );
    }
}
like image 955
marked-down Avatar asked Feb 06 '18 10:02

marked-down


People also ask

Should I use resolver in angular?

Angular Route Resolver is used for pre-fetching some of the data when the user is navigating from one route to another. It can be defined as a smooth approach for enhancing user experience by loading data before the user navigates to a particular component.

What is Runguardsandresolvers?

RunGuardsAndResolverslink type-alias. A policy for when to run guards and resolvers on a route.

What is RouteReuseStrategy?

RouteReuseStrategy : In simple sentence it caches the components and prevent it from reload the components again and again. While in angular to navigate from one page to another page, we have an concept called Routing. By using this we can redirect from one page to another.


1 Answers

First of all.

This is not how you should use a Resolver.

You should use a Resolver only if it is critical necessary for your route that the needed data exist. So a Resolver makes sense when you have a route for a dedicated piece of data and you want to make sure this data exists before you continue with loading the component.

Let's take your Item App as an Example and face the problem from the user point of view.

/items

The user navigates to /items and what he is gonna see first is nothing. That's because the Resolver is fetching all items before the user can reach the component. Only after the resolver fetched all items from the api, the user gets delegated to the ItemsComponent.

But this is not necessary and bad user experience.

Why not delegate the user directly to ItemsComponent. If you have for example an awesome table with lot of buttons and fancy cool css, you can give your app time to render those elements while your Service is fetching the data from the api. To indicate the user the data is loading you can show a nice progress-spinner(-bar) inside the table until the data arrives.

/items/create

Same here. The user has to wait until the Resolver fetched all the data from your api before he is able to reach the CreateComponent. It should be clear that this an overkill to fetch all items just only the user can create an item. So there is no need for an Resolver

/items/:itemId/edit

That's the best place for a Resolver. The user should be able to route to this component if and only if the item exists. But as you defined a Resolver to fetch all items when one of your child routes gets activated, the user faces the same bad user experience as with the previous routes.

Solution

Remove the ItemsResolver from your Routes.

const routes: Routes = [
  {
      path: '',
      component: ItemsComponent,
      children: [
        {
          path: '',
          component: ItemsListComponent,
          data: {
            breadcrumb: 'Items'
          },
        },
        {
          path: 'create',
          component: CreateItemComponent,
          data: {
            breadcrumb: 'Create Item'
          },
        },
        {
          path: ':itemId/edit',
          component: EditOrArchiveItemComponent,
          data: {
            breadcrumb: 'Editing Item :item.name'
          },
          resolve: {
            item: ItemResolverService
          }
        }
      ]
  }
];

You can define a BehaviorSubject in your ItemsService which holds your last fetched Items like a cache. Everytime a user is routing back from create or edit to ItemsComponent your app can immediately render the cached items and in the meanwhile your service can sync the items in the background.

I implemented a small working example App: https://stackblitz.com/github/SplitterAlex/stackoverflow-48640721

Conclusion

A Resolver makes only sense at the edit items route.

Use Resolver cautiously!

like image 166
SplitterAlex Avatar answered Sep 17 '22 12:09

SplitterAlex