Angular – Refreshing a Route with Parameters
Angular routing is pretty nifty. Going into how it works is beyond the scope of this post (and there’s plenty of resources out there doing just that), but suffice it to say if you’re working on an Angular application, you’re using Angular’s routing.
One of the things Angular’s routing does to increase performance is reusing a Component for a route that has already been instantiated. Say you have a Component, “MyComponent”, tied to a route, “/MyPath”. “MyComponent” isn’t created until the user actually navigates to the “/MyPath” route. That makes perfect sense – why instantiate a component that doesn’t need to be used yet? What’s interesting about this design, though, is how parameters factor into it. Let’s say you add a parameter to your “/MyPath” route, making the route “/MyPath/:id”. The first time the user navigates to some version of this route, let’s say “/MyPath/1”, “MyComponent” will be instantiated (with the id parameter set to 1). Then if, without leaving that route, the user changes only the parameter – for example, there’s a link in the component to go to “/MyPath/2”, “MyComponent” will not be recreated. In fact, if not handled correctly, to the user nothing will have changed. It will still look like they’re seeing the “/MyPath/1” version of “MyComponent”.
In this example there are links to two routes set up using an id parameter – “/1” and “/2”, with some text to show which route the component thinks you’re currently visiting. There’s also a timestamp for the purposes of showing when the component was instantiated, and a Refresh button (to be used later in this example).
Without changing anything, if you click between the two route links, you’ll see that the URL is being updated with the appropriate id parameter, but nothing is changing in the component’s output. This is because Angular’s route reuse strategy does not bother to re-created a component if the user is navigating from that same component (even if the parameters change). This is widely known and not really surprising.
The “default” way to handle this is to subscribe to route’s parameters observable and react to any parameters changing via the observable emitting updates. Effectively this means you shouldn’t do any setup logic in ngOnInit() or your constructor for the component, since those will only fire once. Instead, you should react to parameters changing by subscribing to the route parameters observable and do setup logic in there. As a side note, in my opinion this basically means you need to manually “reset” your component to get it back into a blank state, as though the component had just been instantiated, then do setup logic. A lot of components may not necessarily need this but if your setup logic is complicated and depends on certain variables being empty or things like that, you’ll need to do all that reset logic yourself. It’s the price to pay for route component reuse and performance increase.
In the app.component.ts file in the example, if you comment out the 3 lines of code in ngOnInit() that are setting routeNum and currentDateTime, and uncomment the activatedRoute.paramMap subscription, you can see this in action. Now when you click between Route 1 and Route 2, you can see the Route indicator text and the current timestamp update appropriately.
This is all well and good, and for 99% of cases is perfectly fine. However, where this is lacking is if you want to refresh the same route you’re already on. In the example ,this is the purpose of the Refresh button. The refresh button causes the router to navigate to the same route that the user is already one. Because Angular’s routing will reuse a component if it’s already been instantiated, and because the parameter is not technically changing, nothing happens! The parameter change observable doesn’t fire, and the component (of course) is not reloaded. This is not some hypothetical situation – this would be a very common scenario if the user wants to edit some data in a form, save the form, and have the form refresh itself to show any updates that may result from new data.
In the example, if you’ve been following along, you’ll want to go ahead and comment out the parameter subscription and uncomment the block setting the routeNum and currentDateTime (basically reset the example back to its initial state).
Since the parameters observable isn’t going to change when we’re vising the exact same route, we need a way to cause the component itself to re-instantiate. To do this, we’re going to leverage RouteReuseStrategy. Angular’s default RouteReuseStrategy is a class that defines the rules for saving and reusing components as routes are changed.
In the app.module.ts file, there’s a very simple version of a custom RouteReuseStrategy class, and in the AppModule there’s a line that’s commented out that would tell Angular to use our custom RouteReuseStrategy instead of the default one. Go ahead and uncomment that line.
Now that the app is using our custom RouteReuseStrategy, you can click the Refresh button and notice that despite the parameter not changing, the timestamp is updating. Because of this we know that the component is being recreated (in fact you can use the console to view some console logs that show ngOnInit firing, which only happens when the component is instantiated). Note that the refresh() method in our component also sets the router.navigated boolean to false – this needs to be set so that the router thinks the next time any navigation happens, it’s a fresh navigation (and thus the component is created because our RouteReuseStrategy is telling Angular not to save the component for reuse).
You can do a lot with a custom RouteReuseStrategy. In my example I basically “turn off” the Route Reuse Strategy, but you can implement logic that will only save certain components or set custom data in your route definitions and read that data to make it more dynamic, or really anything else you can think of. You can read the documentation here.
Using this method, you can cause a component to be recreated every time there is a route change, whether it is just the parameter changing or even if it happens to be a route navigation to the exact same route. You definitely don’t want to do this all the time in a real-world application since you’ll be taking a performance hit, but it does have very applicable uses.