In a project with over 50 Angular projects we had quite some duplicate code. The first thing you will think of is putting code in a NPM package. But that can be a challenge when some of the 50 had a different implementation.
System context
The applications we make are wizard like applications. We update the title on a route change. This is done by subscribing to the router events.
In the Angular projects we had this code sample in the app.component.ts
:
|
|
Solution
The solution was found using the APP_INITIALIZER
token. This token is used to run a function before the application is bootstrapped. When using this method we can create a singleton service that does not need to be depended upon in the constructor of the components.
The root listener
|
|
The sub services
We have some actions that need to be done on navigation such as logging a page view and setting the title of the page. We can create a sub service for this. This sub service can be injected in the provideRouteHandler
method.
TitleService
|
|
PageViewLogger
|
|
Implementing in your application
By creating a provide method, we can configure any behaviour that is needed for that specific project. And by allowing parameters to implement this method we can extend even for specific business logic for that project. I got this idea when we migrated to a newer version of Angular and my eye fell on the provideRoutes
method. This method is used to provide routes to the application. My solution is based on this method but implemented generic and simple.
So in your main.ts
, you can now register the route handler like this:
|
|
This allows even consumers to hook into those events and write their own subservice for hanlding router events.
Conclusion and discussion
Please don’t use this as an golden hammer. Normally you would want to be as explicit as possible when subscribing to route events. In our case we removed complexity and variations in our codebase. Also I am not sure what kind of performance impact this has on applications. We use these subscriptions at root level, so it was a good fit for us.
Also if you are using my code here, please note it only supports NavigationEnd
events. If you want to support more events, you should edit the design to support a method to in the service if it handles the event type. You could use the Event
type from @Angular/router
to do this.
|
|
Benefits
The move of this duplicate code over all projects also resulted in a higher code coverage and less stress to test the same code over and over again, and creating variants of the same code. It feels a lot like the Loki show of Disney+ where there are multiple Lokis in the same universe. We just restored the sacred timeline by implementing and using the package.
We are flexible in the use of the provide method. Let’s say only one service does not want to log page views in the same way, we can just remove the withPageViewLogger
method from the provideRouteHandler
method. A concrete example may be when logging to application insights in 90% of the projects and 10% of the projects want to log to a different service like Google Analytics or New Relic.
Drawbacks
The drawback is that we have a global singleton that listens to router events. This can be a performance issue when you have a lot of subscriptions. Also the code is not as explicit as it was before. This can be a problem when you are debugging the application. You will have to look at the provideRouteHandler
method to see what is happening.
We also made it very easy to add more services to the route handling. It still can be possible to create duplicate own implementations and when updating the package, the application will break. This is a risk we took and we are aware of it.
References
Endnotes
This design was my idea but I implemented it with a colleague, so he has some of the credit of the code in this post. Jarco, thanks a lot!
This blogpost is the first blog post using my new Dygma Defy keyboard. I did not edit the layers yet just trying to get used to the keyboard.