Sitemap

Managing dependencies among App Initializers in Angular

4 min readAug 6, 2018

--

A friendly maintainability — testing relationship.

Press enter or click to view image in full size
By Google [CC BY 4.0 (https://creativecommons.org/licenses/by/4.0)], via Wikimedia Commons

A few days ago I faced a problem with Dependency Injection in Angular. I had to load some initial configuration data from three different endpoints before App Component is loaded. Endpoint A contains some core configuration such as base URL. However, in order to load configuration from endpoint B and C, I had to get first the base URL from endpoint A.

Angular App Initializer hook

Angular has a hook in its process of initialization called App Initializer. An App Initializer is a special kind of Injection Token — identifier of a dependency — of type Array of Functions that is executed when an application is initialized.

const APP_INITIALIZER: InjectionToken<(() => void)[]>;

This token allows you to provide multiple App Initializers. Each one needs to be provided using a factory that will return the function executed by Angular. This function can return a Promise. If the Promise is resolved then it will continue with the initialization, otherwise, if the Promise is rejected then an error will be thrown and will exit the application.

If you wonder, I had considered at the beginning to just provide three App Initializers in a specific order in which they need to be executed, then I realized that they all are executed at the same time using Promise.all().

const asyncInitPromises: Promise<any>[] = [];Promise.all(asyncInitPromises).then(() => { complete(); }).catch(e => { this.reject(e); });

I came out with three possible solutions for this problem, let’s examine them one by one:

1. Providing only one App Initializer and handling the three requests in the same factory

In this case, I will handle all requests in one single place, configurationFactory. Such factory will be used in the App Initializer provider.

Provide only one factory

The first request is going to be to endpoint A. I’ll make the HTTP request and will convert the Observable returned by get method to a Promise. This is only because is easier to test. Once I get the response from endpoint A, I’ll set configuration data into ConfigurationService instance and then I’ll make requests B and C.

Configuration Factory

The problem that I identified in this solution is that adding or removing a dependent function will force me to touch not only its own function, but also configurationFactory where dependent functions live. Let’s find a way to remove coupling.

2. Providing three App Initializers and creating a ready$ observable in ConfigurationService.

With this second approach, there will be three factories, one for each request. This is good because each factory will have injected its own dependencies only.

Provide multiple factories

As said above, App Initializers will be executed at the same time by Angular, so there must be a way for request B and C to wait until A is ready, since they are not nested anymore.

ConfigurationService will have a ready$ Observable property that will emit a stream when configuration data has been set after request A is resolved. As you can guess, factory B and C will have a subscription to ready$ so they are notified when request A is done.

ready$ Observable inside ConfigurationService

Although we have our requests decoupled, which is a good thing, there are still some issues. Observables added unnecessary complexity to ConfigurationService which will make our implementation harder to understand and test.

Let’s keep factories decoupled but let’s get rid of complexity of Observables in this early phase of the application, keeping ConfigurationService untouched and things simple.

3. Creating custom Injection Token and use Angular’s Dependency Injection system

When it comes to dependency issues, why don’t we use Angular’s Dependency Injection system?, isn’t that what it was made for?

This solution is a combination of solutions one and two. It uses the simplicity of nesting dependent requests of solution one and creating multiple factories from solution two, but in this case I’m going to provide only one App Initializer and a custom Injection Token that will handle factories B and C.

ConfigDeps is the new Injection Token of type (() => Promise<any>)[]. This basically means that the factory used for this provider should return an Array of Functions where each Function should return a Promise. With this definition we can reuse the existing factories as they already create a Function that returns a Promise. All we have to do next is to use the factories for creating the functions, put them into an array and return the array.

Notice that ConfigDeps is now a dependency for configurationFactory. Thus the above array of functions will be injected to configurationFactory. Take a look to the next code.

Provide one App Initializer and a custom Injection Token

Finally, I’m going to inject this new Injection Token into configurationFactory making this factory oblivious of the configuration dependency functions. It just knows that there’s an Array of Functions that need to be executed once request A is resolved. So using map function I create a new Array of Promises and pass it to Promise.all() method.

Configuration Factory with configDeps dependency

Now in your unit test for configurationService you don’t have to create a spy or any other trick for the dependent functions, just pass an empty array for the configDeps.

configFoo = configurationFactory(http, configService, []);

Or a real array of functions for an integration test.

configFoo = configurationFactory(http, configService, [factory(dep1, dep2), functionDefinition]);

Final thoughts

Cool! We ended up with a solution that is maintainable and easy to test at the same time.

Although this is a very specific scenario using App Initializers, the thought process should be the same when solving similar problems that have to do with dependency in Angular.

--

--

Responses (7)