Angular: Refetch data on same URL navigation
By: Kevin Kreuzer
Tags: Front End Development, Angular, Web, Typescript, Angular Router

AngularInDepth is moving away from Medium. More recent articles are hosted on the new platform inDepth.dev. Thanks for being part of indepth movement!

Real-time applications usually use WebSockets or some server push technology. In case the backend doesn’t support a push technology we often use some polling mechanism to get the latest data.

However, there are use cases where we want to give control to the user and let him decide when the data should be refreshed. For example by clicking on a refresh button or a route navigation item while that particular route is already displayed.

By default, the router doesn’t emit router events when we try to navigate to the same URL that is already activated. Which generally is a good thing.

But how can we refetch data on route refresh? 🤔

There are different strategies to solve this problem. Each of them has some advantages and some downsides. Let’s have a look at them.

1. Emit route events on navigation to currently active route

The first approach is to tell the Angular router to emit the route events on refresh and then handle them accordingly in our routed component.

In early Angular versions, there was no option to tell the router to emit events on same route refresh. Angular 5.1 introduced the onSameUrlNavigation property on the routers ExtraOptions.

The onSameUrlNavigation property defines what the router should do if it receives a navigation request to the current URL. By default, the router will ignore this navigation. However, this prevents features such as a “refresh” button. Use this option to configure the behavior when navigating to the current URL. Default is ‘ignore’.

The onSameUrlNavigation property accepts either 'reload' or 'ignore' as values. We need to set this property to reload.

Inside our ExampleRouteComponent we then subscribe to the router events to get notified about the route changes. Notice that the router fires a lot of events while routing. We are only interested in the NavigationEnd and therefore filter out all other RouterEvents .

We subscribe to our filtered router event stream and fetch the data.

The NavigationEnd event gets fired every time we route to a component. It doesn’t matter if it is our ExampleRouteComponent or another one.

Even though our component gets destroyed when we navigate away the current implementation would refetch the data on every navigation. Any ideas why?

Follow me on Twitter or medium to get notified about my newest blog posts!🐥

Although the component gets destroyed, the subscription still survives. We need to ensure the subscription gets unsubscribed when the component gets destroyed.

To unsubscribe on component tear down, we use a Subject in combination with the takeUntil operator. Our Subject fires and completes as soon as the component gets destroyed. Our Subject then triggers the takeUntil operator which unsubscribes from our filtered router events.

Great!! We have implemented the refresh functionality. But wait! How does this behave with nested routes?

Distinguish between child route and parent route navigation

Let’s say our routed component defines its router outlet with routed child components. Guess what happens when we route to a child component?

The router emits the events, and we will again fetch the data because our routed component and its subscription are still alive.

To handle nested routes we somehow need to detect if the user routed to the parent or the child route.

Rx’s pairwise operator emits an array of the previous NavigationEnd and the current NavigationEnd which allows us to compare the URL’s and to distinguish between parent and child routing.

A match between the previous and the current URL represents a refresh. This is the only case we want to fetch data again.

Notice that we kick off the stream by using startWith . Due to the pairwise operator, the initial event would never emit without the startWith operator. No data would be fetched.

Pros of the route events approach 👍

Component decides when to fetch data.

Declarative approach.

Decoupling parent and routed component.

Cons of the route events approach 👎

Handling child routes requires some cumbersome extra logic.

Subscription management — we need to remember to unsubscribe the current subscription on destroy. TSLint rules can support us, but it is still error-prone.

2. Route resolver

We could move our fetch call to a resolver to prefetch data. In some use cases, we are even forced to do so. Resolvers ensure that some data is available before our component gets initialized.

As mentioned in the first approach the router does not emit route events on current route refresh by default. When it comes to resolvers, this means that the resolvers resolve function doesn’t get called either.

We somehow need to indicate to the Angular router that we want to recall this function. To get started, we again need to set the onSameUrlNavigation to reload.

But that’s yet not enough. While this change emits router events, the resolve function is not called again. In addition to the onSameUrlNavigation property we also need to set the runGuardsAndResolvers property on our route accordingly.

The runGuardsAndResolvers property has three possible values.

paramsChange: fires only when route params have changed

fires only when route params have changed paramsOrQueryParamsChange: fires only when a query param or a route param changes

fires only when a query param or a route param changes always: Always fires on navigation to the route

The combination of the onSameUrlNavigation and the runGuardsAndResolvers property is what gets us going. In our example, we set runGuardsAndResolvers to ‘always’.

This will recall our resolve function. The resolver is only called when we route to our RoutedComponent, and therefore no filtering is required. But again, what about child routes?

Resolvers and child routes

Child routes will also trigger our resolvers function. Again, some manual check is required. We need to find out if the current route is refreshed.

Inside the resolver, we assign the previous URL to an internal property. On the next execution of the resolve function, we then check if we want to refresh the current route or if child navigation happened.

Click here to tweet about this article 🐥

In the case of a refresh, we fetch the data. In the case of child navigation, we return an Observable that never emits.

Pros of the resolver approach 👍

No subscription management.

Decoupling parent and routed child.

Cons of the resolver approach 👎

Handling child routes requires some cumbersome extra logic.

Resolvers may introduce worse user experience — it is often better to already display some components layouts and a loading indicator while the data is fetched.

3. Refresh timestamp as queryParam

In this approach, we do not change the router behavior at all. Which means that we don’t emit events on same route navigation. Instead, we add a refresh query parameter that contains a timestamp of the current time.

this.router.navigate(['/routedComponent'], {

queryParams: {refresh: new Date().getTime()}

});

Inside our component, we subscribe to the queryParamMap and refetch data when the next handler is called.

Pros of including refresh queryParam 👍

Router behavior stays untouched.

No extra logic to distinguish between parent and child routing.

Cons of including refresh queryParam 👎

URL changes — contains a refresh parameter.

URL params may be confusing when copied and send to another person. Value of refresh property does not match with the time we fetched the data.

4. Call Method on routed component

Angular’s ViewChild annotation allows you to get a hold of child components and call methods on it.

Even though your routed component exists in the DOM, you can not access it via ViewChild.

But the router-outlet emits an activate event with the activated component. We react to this event to get hold of the currently activated component.

<router-outlet (activate)="setRoutedComponent($event)">

</router-outlet>

Inside our main component we can store it in the following way:

private routedComponent: ExampleRouteComponent; public setRoutedComponent(componentRef: ExampleRouteComponent){

this.routedComponent = componentRef;

}

When the navigation is clicked, we then need to find out if a refresh happened. This check allows us to distinguish between the initial routing and the refresh.

On first routing we usually navigate to our component. The component then is responsible for fetching the data.

As soon as the component gets activated, the router fires the activated event. We use this event and assign the activated component to an internal property.

When we search again, our logic will detect that the route has not changed and will call refresh on the activated component. The refresh will then refetch the data.

Refreshing multiple Routes with Refreshable interface

In case we have various routes that need to be “refreshable” it is quite helpful to have a standard interface for those components. Having this interface allows us to improve readability and type safety.

export interface Refreshable {

refresh: () => void;

}

Our component implements the Refreshable interface and specifies what to do when refresh is called.

export class ExampleRouteComponent implements Refreshable { refresh(){

fetchData();

} }

In our main component, we can then also use the Refreshable interface for our activated components.

private routedComponent: Refreshable; public setRoutedComponent(componentRef: Refreshable){

this.routedComponent = componentRef;

}

Pros of calling child methods 👍

Router behavior stays untouched.

No extra logic to distinguish between parent and child routing

Cons of calling child methods 👎

Coupling — Parent needs to know its children (or at least which method does the refresh).

What to use?

All the illustrated possibilities have their advantages and disadvantages. Pick the appropriate solution based on your use case.

We take advantage of the routers activate event in case we have nested routes. For routes without children, we use the first approach where we filter out the router events.

Tomas Trajan, thanks a lot for your feedback!

🧞‍ 🙏 By the way, click on the 👏🏻 clap 👏🏻button on the left side if you enjoyed this post.

Claps help other people finding it and encourage me to write more posts

Feel free to check out some of my other articles about frontend development.