angulardesign-patternsdependency-injection

Create multiple instances of an angular service


I have an odd requirement. Our's is a single-spa application. In which, the container app would give us a navigation menu on the left (Tree View), and we would create a new instance of the module and component and load it basing on the navigation item the user selects.

We are introducing a new feature called multi-site, wherein, users could connect to multiple databases using a single UI. So, now the left nav would show the available sites and when he expands a site, the list of navigation items would load. When user clicks on a item, we would create a instance of the component and present it to the user. For components, creating multiple instances one for each site is done by the Angular DI itself since we would open each component in tabs. The siteId would be injected into the components while creating the instance.

Whereas the problem is with the Services. As we could place data and business logic in them in observables, etc., I would need to create different instances of the same service for each site.

Is there a way, I could create multiple instances one for each site and use then in the components?

Currently, I have created a simple ServiceManger for each service which would contain a getInstance method, which would create a instance for a service if it doesn't exist and send it back.

@Injectable({ providedIn: 'root' })
// Maintains instances of family service
export class TestServiceManager {
  private readonly api = inject(TestApi);
  private readonly stateInstances = new Map<string, TestServiceManager>();

  /**
   * Gets an instance stored with instanceName param
   * @param instanceName
   */
  getInstance(instanceName: string): TestService {
    if (!this.stateInstances.has(instanceName)) {
      this.stateInstances.set(instanceName, new TestService(this.api));
    }

    return this.stateInstances.get(instanceName) as TestService;
  }
}

In the components, I would inject the TestServiceManager instead of the TestService, and I would access the service as below.

this.testServiceManager.getInstance(this.siteId)

I do not like this for a couple of reasons.

  1. I do not like it that I have to create a service manager for each service.
  2. If one service is being used in the other service, and it also maintains data in it, I would have to create new instance for that as well.
  3. I would have to explain to the new people on the approach we follow everytime and review that they would adhere to it.
  4. Deep down I would have a feeling that I'm not using the default Angular's dependency Injection and thus not folloing the set pattern.

That being said,

Is there a better way of achieving this? I would like to see if I could let angular take care of creating different instances of services for each site basing on some custom provider or something.

If not, could we improve this and create a generic ServiceManager class using which I would not have a need to create a new ServiceManager for each service.


Solution

  • I was able to create a simple custom injector service and achieved this. On demand, I will create an instance of a service and return it. The created instances will be maintained in a Map so that they can be cached instead of creating a new instance for the same context on every request.

    Here is the repository with the sample code.

    Here is the Service factory file that creates the instance.

    We could improve this even further. Currently, the cache key contains the entire service as string. Instead, we can create an md5 hash out of it and use it as key to improve performance.