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 in each file where you need to use Document. 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.

Minimize the amount of changes in a changeset

When creating a new change set, always try to minimize the amount of changes it introduces.

Do this by only modifying what needs to be modified to implement a feature or fix a bug. Keep formatting and refactoring changes for another commit.

The story of how I came to this practice

Many years ago I read Clean Code. One of the things it discussed was to check in our code a little cleaner than when we checked it out. This meshed with the idea of continual improvement through refactoring which I was already following.

I took this principle to heart and made formatting changes, introduced constants, removed commented code and made small refactorings with my change sets. I kept big refactorings for different commits… well most of the time anyways…

Then later on an open source project I contributed to, I was asked during code reviews to keep those changes out of the change sets. I was surprised since I thought I was helping improve the code’s quality. The project’s maintainers reasoning was that the change set needed to include only the changes required to correct the bug that was addressed or implement the new feature we were working on. Refactorings should be kept as different endeavors or for instances where a piece of code was being completely rewritten during the course of normal work.

Over time I started to see this made more sense than my previous practice and eventually started following this practice as well.

One thing I like to do now is keep notes of what changes I want to make, complete my current work and then make a separate commit for formatting and refactoring changes. If I don’t take notes, too often I forget all the little changes I wanted to do and never get around to doing them.

The why

When a commit needs to be read later on to understand the changes, revert them or when doing a blame/annotate to find out why a change was made it’s much easier if all the changes are related to the commit message and associated ticket number.  Otherwise you need to reason for each modification if it was done in relation to the commit’s purpose or as a “bonus” refactoring.

Also, including unnecessary changes makes for longer commits which make for longer code reviews. In my experience the longer the review, the more chances some stuff will get glanced over.

Finally, every change we make, even when having a robust test suite risks introducing regressions. If you later find out your latest changes introduced a bug and it turns out it was in a refactoring, it’s much easier to revert or correct this refactoring without needing to revert a bug fix or feature at the same time.

How to read from .json files from TypeScript / Angular

Locate, or create, 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 we specify the useFactory attribute as a 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';
  }
}

Then in the angular.json file we will add some new build configurations or modify the existing one 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 and inspect the main.js file in your dist folder.

One downside of this approach is that it 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 { }