Recently, we've been migrating a large (and still in progress) Angular 1.x application from JavaScript to TypeScript. This has allowed us to seriously clean up our code, enforce some more consistent patterns across teams, and provide compile time validation within our code. As we migrated, we realized that some of our service classes had a large number of dependencies injected into them, from other logic services to validation services to data services. Each of these services also may have many dependencies. While Angular does create these as singletons and cache them, many of them may only be used for one or two logic methods so it made sense to use a factory and instantiate them lazily. To accomplish this, I created a couple of helpers. Here's how I did it.
My goal was to resolve dependencies lazily, be as reusable as possible, and be testable. I wanted to somewhat model .NET's Lazy<T> so that my Lazy<T> class could be reused elsewhere. I wanted to enforce type safety on my services as much as possible, and I wanted to leverage angular's built in dependency injection without overloading my constructor with tons of dependency parameters. I started with my Lazy<T> class as it would form the foundation for my objects later on.
The Lazy<T> class needs to have a constructor that accepts a function. The <T> parameter defines what the Lazy<T>'s value will be and thus defines what the function will return. It needs to cache the object to make sure the resolver function will only execute once, and it needs to have a .Value property that returns the object. In order to enforce that the constructor's parameter is itself a function with a generic return type we need to create an interface that defines it. I called this interface ILazyInitializer<T> and it looks as follows:
export interface ILazyInitializer<T> {
(): T
}
Now we just need to construct our Lazy<T> which caches the initializer and the instance. This simple class is as follows:
export class Lazy<T> {
private instance: T = null;
private initializer: ILazyInitializer<T>;
constructor(initializer: ILazyInitializer<T>) {
this.initializer = initializer;
}
public get Value(): T {
if (this.instance == null) {
this.instance = this.initializer();
}
return this.instance;
}
}
As you can see, the code above accepts an ILazyInitializer<T> and caches that initialization method. Next it defines a getter that returns type T. If the instance property is null, it sets it, then it returns it. Simple enough. This class contains nothing directly related to angular and could be used for a variety of things, including caching methods that return promises, etc. While temporarily bridging the gap between TypeScript and JavaScript you could even use <any> as your type argument.
Now we need a way to resolve our dependencies. For this I built a simple IocContainerService. This is the single and only dependency that would need to be injected into my services and controllers and would be used to resolve all other dependencies.
The container service is very simple. It injects Angular's built in $injector service and uses it to resolve dependencies. The container has two options, Resolve<T> and ResolveLazily<T>. Resolve immediately resolves and returns your dependency. This is useful for dependencies you may use inside of your constructor and since you're going to be using those immediately, there is no reason to wrap a lazy initializer around them. ResolveLazily does just what it says. It returns a Lazy<T> that will resolve your dependency on the first call to .Value.
It's also important to note that the container service is based on an interface. This means that you could easily fake out the container service in your code and your faked container service could return whatever instances of dependencies you so desire. The interface for the container looks like this:
export interface IIocContainerService {
Resolve<T>(resourceName: string): T;
ResolveLazily<T>(resourceName: string): Lazy<T>;
}
And the implementation is as follows:
class IocContainerService implements IIocContainerService {
public static $inject: string[] = ['$injector'];
constructor(private $injector: any) {
}
public Resolve<T>(resourceName: string) : any {
return this.$injector.get(resourceName) as T;
}
public ResolveLazily<T>(resourceName: string): Lazy<T> {
return new Lazy<T>(() => this.Resolve<T>(resourceName));
}
}
Assuming you've registered the IocContainerService as 'iocContainerService', the usage is simple. Let's create a logic service called WidgetLogicService. This service depends on the WidgetDataService to get data about widgets. It also depends on the WidgetValidationService which is only used on save. Let's assume that the WidgetDataService and the WidgetValidationService also depend on several other services, which in turn may depend on several other services which would make the dependency resolution chain somewhat long.
In fact, let's take it one step further. While this would not be good practice in nearly all cases, let's assume that the WidgetDataService depends on the WidgetValidationService, but the WidgetValidationService also depends on the WidgetDataService. How could this be? That would create a circular dependency! However, a long as each of those services used Lazy instantiation and didn't actually use the other service within its constructor, this would be perfectly acceptable and no longer result in any errors as one would always have to be constructed before attempting to call the other.
Here's how our WidgetLogicService would look without lazy initializers:
class WidgetLogicService: IWidgetLogicService {
public static $inject: string[] = [
'widgetValidationService',
'widgetDataService',
];
private WidgetData: IWidgetDataService;
private WidgetValidation: IWidgetValidationService;
constructor(private WidgetData: IWidgetDataService, private WidgetValidation: IWidgetValidationService) {
}
}
The above simply injects the two services as their private members. This requires the least amount of code, but all dependencies will be resolved as soon as this service is constructed. The two injected services also could not depend on each other as they would create a circular reference unless they were internally using lazy instantiation. Now imagine what the constructor would look like if the logic service depended on 10 other services like $timeout, $q, etc.
Now let's take another look at the WidgetLogicService, but this time we are going to use Lazy<T> so we can resolve those dependencies only when we actually need to use them. We will define our private members as Lazy and we inject only the container and use ResolveLazily to create our initializers. The updated listing from above would look like this:
class WidgetLogicService: IWidgetLogicService {
public static $inject: string[] = [
'iocContainerService'
];
private WidgetData: Lazy<IWidgetDataService>;
private WidgetValidation: Lazy<IWidgetValidationService>;
constructor(iocContainer: IIocContainerService) {
this.WidgetData = iocContainer.ResolveLazily<IWidgetDataService>('widgetDataService');
this.WidgetValidationService = iocContainer.ResolveLazily<IWidgetValidationService>('widgetValidationService');
}
public ValidateAndSave(widget IWidget) {
if(WidgetValidation.Value.IsValidWidget(widget)) {
WidgetDataService.Value.SaveWidget(widget);
}
}
}
Here you can see that we've changed our signature to only take in the container. We've also added a new example method called ValidateAndSave. This method uses the .Value property of the lazy initializer to resolve the dependency and then execute a method on it. Realistically, you would have different services spread across different methods which may or may not be called, in which case lazy initialization makes sense as you only resolve those dependencies when you actually first need them.
And there you have it, a quick and easy way to initialize dependencies lazily with type safety. Any feedback is always appreciated.