Picture of New York which represents complexity of angular application.
Photo by Lukas Kloeppel from Pexels

Famous Angular forRoot pattern

Recently I came across this strange bug in one of my applications. I had a service that in my mind was a singleton in the scope of the whole application. It was a basic service that holds part of the application state (built with Subject, so all subscribers get changes in a reactive way). The bug which I was trying to find was a common one. What I mean by that is that when I tried to set a value on this service, not all subscribers were notified about it. When I started debugging I figured out in no time that this singleton service was in fact not a singleton. It had been provided at more than one level of the application. So I created a commit with the fix and pushed it to the repo. Then I started to think about how to prevent this situation from happening again. I feel that as developers we are obliged to make solutions that will help us in the future. Here are some of my thoughts on how to deal with such an issue.

Standard angular service is called singleton by default, but can you actually call it singleton? The angular dependency injection mechanism is one of the best parts of the framework. It handles managing services for us, it creates them, deals with life cycle hooks. The overall dependency injection mechanism gives us developers lots of flexibility, but we need to keep in mind that it has to be used with caution.

Dependency injection in Angular

Angular DI was built in a way that enables us to create only one instance of a service per context (context is the keyword in that sentence). I guess we all know that Angular DI is hierarchical, so modules and components create different levels of contexts. If we provide a service at the AppModule (aka "root", I will call it the root level in the rest of this article) level it is available to all components and services used in the application. When we provide a component at the component level, it has this component context and it can be used by it and all of that component's children. This is a great and powerful feature of Angular, but like everything which is powerful, it has to be used thoughtfully.

With great power comes great responsibility.
With great power comes great responsibility.

Like most of you, I work in a team with developers who are at different levels of seniority. I also feel that my programming skills change depending on the time of night, at 3 am I need to google basic syntax for if statements. From my experience, every programmer regards experience knows the basic principles of the singleton pattern. Angular gives us sometimes to much flexibility and that may cause misunderstanding even in such a simple matter as singleton service. So it is extremely important to force rules upon a project which helps developers better understand the framework and create code with fewer bugs. So let's see if there is a way to force angular to allow the creation of only one instance of the service for the whole application.

Singleton

Let's firstly take a look at the Object-Oriented design pattern singleton:

class Service {
	static getInstance(): Service {
		if (!Service.instance) {
			Service.instance = new Service();
		}
		return Service.instance;
	}
	private static instance: Service = null;
	private constructor() {}
}

As you can see, this pattern allows us to create an object not by a new operator, but only by a static method. This method checks the condition whether the service has been instantiated earlier and based on that either creates a new object or returns the previous one. This is the most basic mechanism to guard against the object of a class from being created multiple times. Let's see how it relates to the angular world.

Angular modules

There is this commonly used practice which says that Angular application architecture should be module driven. What I mean by that is when you create a component, that presents a list of users you don't simply declare that component in the AppModule, instead you should create a whole NgModule which represents the User feature. In that module, you should keep all the components, directives, services, pipes and other modules required by the User feature module. An NgModule declared like that represents the whole feature package. This way when you need to change some of the user component inner parts by e.g. adding more child components to it, you don't have to modify every import of that component. The same goes for the wide reusable services.

When you want to provide a service that then will be used in the whole application you simply have to import related modules at the root level. All modules and declared components provided at the root level are available from the start of the angular application. What about services? Services are created when someone asks for them. So when other service or component simply injects a service class in the constructor, angular create that service upon the creation of the component. If no one asks for a specific service, it will never be instantiated.

So does it mean that importing a module at the root level makes provided services singletons in all cases? Let's think about it.

Modules with lazy routes

Most of the angular applications are SPA's which consist of many different routes. Since the early stages, angular allowed us to create lazy routes. It's very simple to do it in the current version of the framework. In the place where you declare your routes you just need to specify a module instead of the component. A resource provided this way is loaded lazily, only after the user activates the route. When the files are downloaded from the server, angular dynamically adds a new route to the application tree. So that is a really interesting mechanism from the dependency injection point of view because what happens with services added dynamically to the context and how does it affect the root context?

We said earlier that services provided at the root level are available to the whole application. What about lazy modules?

Images present basic angular root context
Basic angular root context.

Lazy loaded modules create a hierarchical dependency tree at the module level. So that means that services provided at the different routes are available only to the context of that route.

A similar thing happens when someone accidentally provides a root-level service at the lazy route level. That creates a situation in which the whole application uses one instance of the service except for the one route which uses its own instance of the service mentioned. This creates the possibility of many hard to find bugs one of them has been described at the beginning of this article.

Images present basic angular root context with many lazy routes
Angular root context with many lazy routes.

The forRoot pattern

In the Angular documentation, you can find one way of dealing with this kind of issue. Its called "The forRoot() pattern"https://angular.io/guide/singleton-services#the-forroot -pattern and is a convention which says that root modules should be imported with the static forRoot method. In addition modules at the "route" level should be imported with the forChild method. Let's look at an example:

class NgModule {
	static forRoot(): ModuleWithProviders {
		return {
			module: NgModule, providers: [..rootProviders]
		};
	}
	static forChild(): ModuleWithProviders {
		return {
			module: NgModule, providers: [..routeProviders]
		};
	}
}

We can see that the forRoot method returns NgModule with for root-level providers and the forChild method returns NgModule without services which should be singleton at the root level (root context). This is a great convention and it should be respected, but there is no mechanism that prevents us from breaking it. Is there a way to force this convention programmatically?

One of the methods proposed in the Angular documentation angular.io is to inject module in its own constructor:

constructor (
	@Optional() @SkipSelf() parentModule?: Module) {
	if (parentModule) {
		throw new Error( 'Module is already loaded.');
	}
}

This clever solution will not allow us to import NgModule at the lazy route level. But it doesn't allow reuse module at the forChild level.

Angular service as a singleton

Let's get back to the origin of the singleton. We can modify root services to allow only one instance of the service:

class Service {
	private static created = false;

	constructor() {
		if (Service.created) {
			throw new Error('Service is already created.');
		}
		Service.created = true;
	}
}

Both of these solutions may look interesting at the start and are good in many cases, but they, unfortunately, fail when it comes to inheritance. Creating many versions of services that all come from one derived class can make another big bug potential.

Module with singleton providers

If service is not the area maybe let's move this solution to the module itself:

class NgModule {
	private static created = false;

	static forRoot(): ModulesWithProviders {
		if(NgModule.created) {
			return throw new Error('NgModule.forRoot() used more than once');
		}

		return { module: NgModule, providers: [...rootProviders] };
	}

	static forChild(): ModulesWithProviders {
		return { module: NgModule, providers: [...lazyProviders] };
	}
}

This solution allows us to use forRoot method only once in the scope of our application. If someone uses forRoot more than once, he will receive a runtime error, with the message specific to the problem which he has generated. So this technique prevents having two root level services at the different parts of the application. Apart from that, it allows us to use services that are meant to be provided at the lazy module level of the application. These services that have to have many instances should be provided with the forChild method.

Images present complex angular context with many lazy routes
Complex angular context with many lazy routes.

Unfortunately, this solution is not AOT friendly. We need to make a small adjustment so Angular compiler knows how to compile the code. Let's have a look at the working example:

export function factory() {
	const platformModuleCreated = (factory as any)._platformModuleCreated || false;
	if (platformModuleCreated) {
		throw new Error('PlatformModule.forRoot imported to many times');
	}
	(factory as any)._platformModuleCreated = true;
}

@NgModule({
	imports: [
		CommonModule,
		RouterModule
	],
	declarations: [
		PlatformLinkComponent
	],
	exports: [
		PlatformLinkComponent
	]
})
export class PlatformModule {

	private static rootUsed: boolean = false;

	static forRoot(): ModuleWithProviders {
		return {
			ngModule: PlatformModule,
			providers: [
				...providers,
				{
					provide: 'PlatformModuleInstance',
					useFactory: factory
				}
			]
		};
	}

	static forChild(): ModuleWithProviders {
		return {
			ngModule: PlatformModule,
			providers: []
		};
	}

	constructor(@Inject('PlatformModuleInstance') instance: any) {
	}
}

This is a similar solution, but instead of static properties, it stores information on the factory function. It adds some complexity to the code, so it should be used only when it is required.

Summary

Angular dependency injection is a great and powerful mechanism. It gives us a lot of awesome functionalities, but they sometimes require introducing some conventions and additional programmatic mechanisms to deal with potential issues, that may come from so much flexibility. In my opinion we angular developers should be aware of potential issues and we should adjust our architecture and solutions to deal with them in the best way.

Luke, Software engineer, passionate about best practices, patterns and architectures for modern web
				development, open-source creator, Angular & TypeScript enthusiast, Generic UI Technical Blog

Luke

Software engineer, passionate about best practices, patterns and architectures for modern web development, open-source creator, Angular & TypeScript enthusiast

We use cookies to improve your experience. If you continue browsing, we assume that you consent to our use of cookies.