The Bones of a Multi-Tenant Angular Application

The first thing we need to do to establish multi-tenancy is to create a set of components that will be responsible for managing the current tenant in our application. It will be responsible for the following tasks:

Determining who the current tenant is, based on the URL. Decorating the headers we send back to the server part of our application with a special header that lets the backend know which tenant we are acting on behalf of.

From these two simple features we will then enable the following multi-tenant features:

Skinnable components, based on the tenant. Automatic injection of variants of services, based on the tenant. Automatic, module-level reconfiguration of routes, based on the tenant.

Of course, in concept more features are possible, but this article only considers these two.

To begin, let’s fire up a new angular application:

# ng new multi-tenant-angular

This will create a fresh, but blank new Angular application which will serve as the basis of what we will build up.

Building the Tenant Service

The first thing we need to do is build a service that can determine what the correct tenant is for a given URL. Establishing tenancy this way is a pretty common pattern, and here’s how it works.

First, consider a URL like the following:

http://tenant-name.applicationdomain.dom

In the above URL, the first part of the domain (tenant-name) is what gives us the tenant. This allows us to build out a URL structure like, for example:

Where client1, client2, and client3 are all tenants of our application. To implement this functionality, let’s create a TenantService within our application in a module we use to hold all the tenant functionality. We can do all that with the following two commands.

# ng generate module tenant

# ng generate service tenant/tenant

The first command will create the module TenantModule in the file src/app/tenant/tenant.module.ts and the second command will create the service TenantService within the file src/app/tenant/tenant.service.ts .

For now we won’t have to do anything to do the tenant module, and below we provide our minimum viable implementation of the service.

In the file src/app/tenant/tenant.service.ts

Basic implementation of the TenantService.

In the above definition we provide methods for three things:

Getting the current tenant, based on the hostname. Adding a special tenant header to a set of HttpHeaders. Defining a set of enums for our different Tenants.

This class can now be injected anywhere you need it. In the next section we are going to do that to inject our custom headers.

Decorating Headers with Tenant Information

So now that we’ve sketched out the basics of a multi-tenant application, you can now inject your TenantService wherever you need it. The goal of the TenantService is not to perform different actions based on the tenant, but to inject different functionality. We are going to cover how to do that in our application in the next section, but before we do let’s cover another small details: handling multi-tenancy on the backend.

To handle multi-tenancy on the backend, we are going to decorate any HTTP requests we send to the backend with a special header that indicates the current tenant for the request. To do that, we will use a HTTP interceptor.

To do this in your project you will first create the interceptor and then wire it up in your module.

First, create the file tenant.interceptor.ts in the src/app/tenant directory. Your file should look like the following:

Our tenant interceptor.

In the above file we simply intercept the HTTP request and add a header to it that indicates what the current tenant is. It is up to the backend to do something different depending on the tenant.

Next, wire up the interceptor in your tenant.module.ts:

Our updated tenant module.

After making these two changes, any requests to your backend will automatically add the correct tenancy header.

Dynamically Injecting Services Based on the Tenant

Ok, now for something a little more interesting. Suppose that in our application we have a Service that is responsible for implementing login. For client1, the service should work one way, but for client2 the behavior is different but similar to client1.

From an OOP standpoint, the design we would use is simply to create a base class, say, LoginService , and derive two classes from it, say, Client1LoginService , and Client2LoginService . How can we achieve this?

The pattern I will detail in this section can be repeated for any situation wherein you need to inject services like this. The basic ingredients are:

It needs to be in a module. To make this work, we will be hooking into the provider features of Angular. We can easily do this at the module level. You need to define a factory. Within the module, when you configure the provider, you’ll configure a factory that gets the correct instance of your service.

So let’s get started.

To begin, create a new module called LoginModule and create three services:

LoginService: The base class Client1LoginService: The service, specialized for client1. Client2LoginService: The service, specialized for client2.

We can do that with the following sequence of commands:

# ng generate module login

# ng generate service login/login

# ng generate service login/client1login

# ng generate service login/client2login

For simplicity, let’s assume that the difference between the two implementations is that client1 will need a localStorage key set, where as client2 doesn’t.

First, we need to define the base LoginService, which needs to include the factory for accessing the correct instance of the login service.

The following listing implements that functionality in login.service.ts:

Implementation of the LoginService.

There are a few things to note about the above implementation:

Unlike most services, we have removed the Injectable annotation. That’s because you can’t inject this service directly. At the top of the file, we define a factory, which we will use later to inject the correct version of the service. Note that we inject handles to the services we will return, dynamically. In the LoginService, which we make abstract, we define a single method login which will just return a boolean indicating if the login was OK.

Next, we implement the two child classes in the normal way, extending from the LoginService:

Implementation of the Client1 Login service.

Implementation of the Client2 Login service.

Ok, now, to tie it up, we need to update the module definition for the login module. We do that in the following listing for login.module.ts:

The definition of the login module, including the provider definition.

Notable in the listing for the login module is that we have explicitly defined a provider for LoginService. To use it, you simply inject LoginService wherever you want to reference the login service for the client. For example:

constructor(private loginService: LoginService) {}

Make sure to add the Login module to the list of imports for your module!

Note: Don’t ever try to explicitly inject the derived login classes: let the factory handle that!

Dynamically Changing Routes Based on the Tenant

In addition to dynamically injecting services, at times you may want to dynamically change the routes provided by a module. Building on our LoginModule in the last section, in this section we demonstrate how to dynamically switch routes available in an application based on the Tenant.

The trick to this is to implement this switching logic in the constructor to the LoginModule. In the following listing we demonstrate how this can be accomplished:

Our updated LoginModule.

Dynamically Styling Components Based on the Tenant

So far in the article we have seen how some very simple concepts have allowed us to inject some fairly complex dynamic behavior based on our implementation of multi-tenancy. In this section we will turn our attention to styling components based on the tenant.

However, before we jump into the code, let’s talk about the idea that makes it all work.

The trick behind our approach is to use a CSS selector host-context on the root application component. This selector will then be accessible to any component loaded into the application root component. Here’s what it looks like in practice:

In app.component.ts

Using themes in the application.

In the above listing, there are several things to note:

The component implements OnInit . This is important, because we want to establish the tenant (and therefore the styles) when the component is loaded. We use the HostBinding annotations to selectively enable the theme-client1 or theme-client2 classes. In the ngOnInit function we enable the theme, based on the tenant.

To write the theme for this configuration, you simply provide a style sheet like the following:

Simple style sheet for our application consisting of two skins.

The nice thing abut this configuration is that the code that we showed in the application component is the only place the initialization code needs to exist. Any components within the module only need to provide css selectors for the various themes. That is, in each component you create, you just supply host-context styles for your component.

Conclusion

In this article we covered the basics of making a multi-tenant Angular application. Specifically, we covered:

How to create a basic tenant service. How to decorate outgoing headers with the current tenant. How to dynamically inject the correct instance of a class, based on the tenant. How to dynamically adjust the routes of an application, based on the tenant. How to style the application, based on the tenant.

I hope you find this article useful. If you have any suggestions, questions, or comments, please feel free to drop it in the comments, below!

If you’d like to play around with the application developed in this article, you can get the source over on GitHub, here: https://github.com/jsinglet/multi-tenant-angular

To test locally, make sure to create hosts file entries for client1 and client2 like the following:

In hosts :

127.0.0.1 client1.localhost

127.0.0.1 client2.localhost

When you access the app locally, make sure to do it via one of those hostnames for tenancy to work correctly.