Angular’s dependency injection is a great boon to productivity, but even it has its limits.
While not an everyday occurrence, it is quite possible to come across a circular dependency.
This happens when service A injects service B, but service B in turn injects service A, usually indirectly.
For example, B depends on service C which depends on A –
A -> B -> C -> A forms a nice little circle.
When Angular’s bootstrap process tries to setup all the services and inject each service’s dependencies, it detects when such a scenario happens and errors out, instead of getting stuck in an infinite loop.
In most cases, circular dependencies are code smells for design that could be made clearer. Most likely you have a service that’s gotten too big, and splitting it will result in cleaner code and no circular dependency.
But, there are a few common scenarios that come up in a lot of apps where some kind of circular dependency makes sense. Let’s look at an example and a solution.
A real-world circular dependency
Enter HTTP interceptors. Interceptors are Angular’s very handy tool for handling cross-app concerns when it comes to handling HTTP requests and responses. They are probably most often used for handling authentication.
I’ve come across circular dependencies showing up in interceptors at several clients. It usually goes something like this:
As part of implementing the authentication mechanism of the app, we create an interceptor to be in charge of handling the different responses. One of the behaviors it needs depends on an external service, which in turn makes an HTTP request.
For example, we would like to redirect the user to a login page on every 401 error.
An interceptor watches for these errors, and once it sees them it calls our
AuthService to tell it to handle an expired session.
AuthService depends on
$http for some reason, like performing a login request.
A naive setup would look somewhat like this:
1 2 3 4 5 6 7 8 9 10
1 2 3 4 5 6 7 8 9
If you’ll try running an app with this code, Angular will spit out an error:
Error: [$injector:cdep] Circular dependency found: $http <- AuthService <- $http.
$http depends on our interceptor, which depends on
AuthService, which depends on
(Are you getting dizzy, too?)
$injector to the rescue
Just for cases like these Angular provides us with the
The injector is the programmatic way to access Angular’s dependency injection.
Using it, we can manually inject
AuthService inside our interceptor and break the circular dependency:
1 2 3 4 5 6 7 8 9 10
$injector.get('AuthService') will return the exact same singleton instance of
The main difference is that now we are performing this at a later point, after Angular has finished bootstrapping the project.
At this point in time, where everything is up and running, it’s safe to inject
Thus we have effectively broken out of the circular dependency.
“Maintaining AngularJS feels like Cobol 🤷…”
You want to do AngularJS the right way.
Yet every blog post you see makes it look like your codebase is obsolete. Components? Lifecycle hooks? Controllers are dead?
It would be great to work on a modern codebase again, but who has weeks for a rewrite?
Well, you can get your app back in shape, without pushing back all your deadlines! Imagine, upgrading smoothly along your regular tasks, no longer deep in legacy.
Subscribe and get my free email course with steps for upgrading your AngularJS app to the latest 1.6 safely and without a rewrite.