Using a type alias to create a new name for a preexisting type like Document

I often need to work with auto-generated types that have been generated by Swagger based on an API definition.

These types sometimes clash with the names of preexisting types. For example the Document object, which normally refers to: Any web page loaded in the browser and serves as an entry point into the web page’s content, but in my case is also the name of an interface generated by Swagger.

Since you don’t need to import the Document type to use it in TypeScript / Angular projects, every time I try to use the generated Document class it’s referring to the DOM’s Document by default.

This can be fixed by adding an import statement, but to avoid dealing with this project-wide we can use type aliases which let us give a type a new name:

type MyAppDocument = Document;

Since I can’t change the generated Document file itself, it would get overwritten the next time it’s generated, I create a type-alias.ts file like this:

import { Document } from './document';

export type MyAppDocument = Document;

Which then allows me to use the new MyAppDocument type everywhere.

How to read from .json files from TypeScript / Angular

Locate your typings.d.ts file and add the following lines:

declare module "*.json" {
  const value: any;
  export default value;
}

Otherwise you will get an error saying the compiler can’t find module filename.json.

Then you can include a .json file in your project. For example this file:

{
  "version": "6.01"
}

And you can read from such a file like this:

import * as versionData from '../version.json';

// ...

if (versionData && (versionData).version) {
      this.version = (versionData).version;
}

Controlling service injection per configuration in Angular

In the previous post I mentioned we could use a factory method and the useFactory attribute of the Injectable decorator to read which service to inject from some sort of configuration file.

To do this we will configure our factories per build configuration. Controlling dependency injection per configuration allows us to to things like injecting different services for different clients/versions or injecting different services for dev and prod.

We will start by adding a services.factories.ts file to our environments folder (if you created your app with Angular Cli, otherwise just add it wherever you please) and adding the following service factory in it:

import { FirstService } from 'src/services/first.service';

export let mainServiceFactory = () => {
  return new FirstService();
};

// include factory methods for the other services that need customization

We will need to add one factory method per service we want to customize.
Then we add a similar file called services.factories.alt.ts that uses our alternate services:

import { SecondService } from 'src/services/second.service';

export let mainServiceFactory = () => {
  return new SecondService();
};

In our services themselves, here I’m reusing FirstService from the previous posts, we specify the useFactory attribute in our decorator:

import { Injectable } from '@angular/core';
import { mainServiceFactory } from 'src/environments/service.factories';

@Injectable({
  providedIn: 'root',
  useFactory: mainServiceFactory
})
export class FirstService {

  constructor() { }

  output() {
    return 'first';
  }
}

For the SecondService mentioned in the factories, I’m again reusing the service from the previous posts.

Then in the angular.json file we will add some new build configurations or modify existing ones to customize the services per configuration.

Since the angular.json file can get quite lengthy, I’ll only include the important bits.

// ... under architect / build, after options
"configurations": {
            "altServices": {
              "fileReplacements": [
                {
                  "replace": "src/environments/service.factories.ts",
                  "with": "src/environments/service.factories.alt.ts"
                }
              ]
            },
            "production": {
// ... normaly found after build
"serve": {
          "builder": "@angular-devkit/build-angular:dev-server",
          "options": {
            "browserTarget": "dependency-injection:build"
          },
          "configurations": {
            "altServices": {
              "browserTarget": "dependency-injection:build:altServices"
            },
            "production": {
              "browserTarget": "dependency-injection:build:production"
            }
          }
        },
// ... if you're using e2e tests
"e2e": {
          "builder": "@angular-devkit/build-angular:protractor",
          "options": {
            "protractorConfig": "e2e/protractor.conf.js",
            "devServerTarget": "dependency-injection:serve"
          },
          "configurations": {
            "altServices": {
              "devServerTarget": "dependency-injection:serve:altServices"
            },
            "production": {
              "devServerTarget": "dependency-injection:serve:production"
            }
          }
        },

By using ng serve or ng build, the regular services will be used. To switch to the other services we need to use the -c switch like such:

ng serve -c altServices
ng build -c altServices

Note that the unused services are tree shaken, meaning they won’t appear in the final bundle. To check this for yourself do a ng build, ng serve won’t do, and inspect and the main.js file in your dist folder.

One downside of this approach is that this creates a circular reference between the file containing the factory method and the service itself. To fix this you can add a third class, a service registry which contains the services and has no dependency. The service factories will register themselves with this registry and then the services will only depend on the registry and not the factories themselves. See this SO question.

Instead of using factories, we could also use providers instead. This would allow us to only create one provider and set it up in one place, in our @NgModule providers attribute rather than in all services we want to customize. The downside to this is that it prevents tree shaking of any sort.

Let’s show how we would do this.

We would create a service.providers.ts and service.providers.alt.ts file instead of the service.factories.ts. Here’s an example of such a file:

export let servicesProvider = [
  { provide: FirstService, useClass: FirstService },
  { provide: GraphicsService, useClass: GraphicsService },
];

Remove the useFactory attribute in our service and then configure our AppModule to use this provider:

@NgModule({
  declarations: [
    AppComponent
  ],
  imports: [
    BrowserModule
  ],
  providers: [servicesProvider],
  bootstrap: [AppComponent]
})
export class AppModule { }

 

Using a factory method to control Angular dependency injection

Similar to the use of the useClass attribute in the previous post, we can also use the useFactory attribute to supply a factory method to create our dependent value.

To start off, here’s a simple illustration of using the useFactory attribute.

We need to create a factory method which will use some logic to determine which service should be created. In this case checking if the Date.now(), which includes the time information, is even. This is of course a rather contrived example.

import { FirstService } from './first.service';
import { SecondService } from './second.service';

export let serviceFactory = () => {
  if (Date.now() % 2 === 0) {
    return new FirstService();
  } else {
    return new SecondService();
  }
};

In the first service we add the useFactory attribute to the service’s decorator:

import { Injectable } from '@angular/core';
import { serviceFactory } from './service.provider';

@Injectable({
  providedIn: 'root',
  useFactory: serviceFactory
})
export class FirstService {

  constructor() { }

  output() {
    return 'first';
  }
}

Here is the code for SecondService:

@Injectable()
export class SecondService implements FirstService {
 
  constructor() { }
 
  output() {
    return 'second';
  }
}

And now any component that injects a FirstService through it’s constructor will use the serviceFactory method which could contain any logic we require. This could include reading the service information from a configuration file in the project, allowing us to inject different services based on which configuration file the project was built with.